文章目录

01. 介绍

1.1、三层架构

三层架构包含的三层:

  • 界面层(User Interface layer):主要功能是接受用户的数据,显示请求的处理结果。使用 web 页面和 用户交互,手机 app 也就是表示层的,用户在 app 中操作,业务逻辑在服务器端处理。
  • 业务逻辑层(Business Logic Layer):接收表示传递过来的数据,检查数据,计算业务逻辑,调用数据访问层获取数据。
  • 数据访问层(Data access layer):与数据库打交道。主要实现对数据的增、删、改、查。将存储在数据库中的数据提交 给业务层,同时将业务层处理的数据保存到数据库。
三层架构 对应的包 对应的类 对应的高级框架
界面层 controller(控制器) xxxServlet类 SpringMVC
业务逻辑层 service(服务) xxxService类 Spring
数据访问层 dao(Data Access Object数据访问对象) xxxDao类 MyBatis

1.2、MyBatis

MyBatis是一个优秀的持久层框架,它对jdbc的操作数据库的过程进行封装,使开发者只需要关注 SQL 本身,而不需要花费精力去处理例如注册驱动、创建connection、创建statement、手动设置参数、 结果集检索等jdbc繁杂的过程代码。

Mybatis通过xml或注解的方式将要执行的各种statement(statement、preparedStatemnt、CallableStatement)配置起来,并通过java对象和statement中的sql进行映射生成最终执行的sql语句,最后由mybatis框架执行sql并将结果映射成java对象并返回.


02. jdbc问题总结

  1. 数据库连接创建、释放频繁造成系统资源浪费,从而影响系统性能。如果使用数据库连接池可解决此问题.
  2. Sql语句在代码中硬编码,造成代码不易维护,实际应用中sql变化的可能较大,sql变动需要改变 java代码.
  3. 使用PreparedStatement向占有位符号传参数存在硬编码,因为sql语句的where条件不一定,可能多也可能少,修改sql还要修改代码,系统不易维护.
  4. 对结果集解析存在硬编码(查询列名),sql变化导致解析代码变化,系统不易维护,如果能将数据库记录封装成pojo对象(持久化类)解析比较方便.

03. 快速使用MyBatis

<mark>这里没有使用到任何开发模式,只是描述了快速使用MyBatis的步骤</mark>。

3.1、项目结构

3.2、基本使用步骤

3.2.1 概述

  1. 导入jar包:mybatis、mysql、lombok(在pom文件中加入mybatis坐标)
  2. 创建实体类(每一张表对应一个类,也叫pojo类(Plain Old Java Object:持久化类))
  3. 创建sql映射文件:mapper_xxx.xml文件(一个表对应一个映射文件)
  4. 创建mybatis主配置文件:SqlMapConfig.xml文件(一个项目对应一个主配置文件,别忘了在里面指定mapper文件)
  5. 创建使用mybatis的对象Sqlsession,通过它的方法执行sql语句

3.2.2 导入jar包

MyBatis

<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.4.1</version>
</dependency>

MySQL

<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.18</version>
</dependency>

Servlet、Jsp

导入Servlet、jsp包后就不需要手动导入tomcat里面的依赖包了。

<!-- servlet的依赖 -->
<!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api -->
<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>3.1.0</version>
    <scope>provided</scope>
</dependency>

<!-- jsp的依赖 -->
<!-- https://mvnrepository.com/artifact/javax.servlet.jsp/jsp-api -->
<dependency>
    <groupId>javax.servlet.jsp</groupId>
    <artifactId>jsp-api</artifactId>
    <version>2.1</version>
    <scope>provided</scope>
</dependency>

Lombok

Lombok能以简单的注解形式来简化java代码,提高开发人员的开发效率。例如开发中经常需要写的javabean,都需要花时间去添加相应的getter/setter,也许还要去写构造器、equals等方法,而且需要维护,当属性多时会出现大量的getter/setter方法,这些显得很冗长也没有太多技术含量,一旦修改属性,就容易出现忘记修改对应方法的失误。

Lombok能通过注解的方式,在编译时自动为属性生成构造器、getter/setter、equals、hashcode、toString方法。出现的神奇就是在源码中没有getter和setter方法,但是在编译生成的字节码文件中有getter和setter方法。这样就省去了手动重建这些代码的麻烦,使代码看起来更简洁些。

<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.12</version>
    <scope>provided</scope>
</dependency>

需要双击该jar包安装好后才能使用(路径选择为D:\Developer_Tools\eclipse\eclipse\eclipse.exe

注解 说明
@Data @Data注解在类上,会为类的所有属性自动生成setter/getter、equals、canEqual、hashCode、toString方法,如为final属性,则不会为该属性生成setter方法。
@Getter/@Setter 如果觉得@Data太过残暴(因为@Data集合了@ToString、@EqualsAndHashCode、@Getter/@Setter、@RequiredArgsConstructor的所有特性)不够精细,可以使用@Getter/@Setter注解,此注解在属性上,可以为相应的属性自动生成Getter/Setter方法
@Cleanup 该注解能帮助我们自动调用close()方法,很大的简化了代码。
@ToString 类使用@ToString注解,Lombok会生成一个toString()方法,默认情况下,会输出类名、所有属性(会按照属性定义顺序),用逗号来分割。
@NoArgsConstructor 生成无参构造
@AllArgsConstructor 生成全参构造

使用示例

@Getter					// get方法
@Setter					// set方法
@NoArgsConstructor		// 无参构造
@AllArgsConstructor		// 全参构造
@ToString				// tostring方法
public class Employees {
   
    private Integer employeeNumber;
    private String lastName;
    private String firstName;
    private String extension;
    private String email;
    private String officeCode;
    private Integer reportsTo;
    private String jobTitle;
}

3.2.3 创建表对应的Pojo类

package com.atSchool.Pojo;

/** * house数据库中sys_user表对应的实体类 */
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class HouseUser {
   
	private Integer UID; // 这里必须和表中的列名一样
	private String uName;
	private String uPassWord;
}

3.2.4 添加配置文件

<mark>都是在项目的/MavenProject/src/main/resources目录下创建</mark>

SqlMapConfig.xml

内容在如下网址中有

https://mybatis.org/mybatis-3/zh/getting-started.html

示例:SqlMapConfig.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">

<!-- configuration中可以有很多的标签,他们的顺序是固定的,不能随意更改 -->
<configuration>
    <!--控制mybatis全局行为的-->
    <settings>
        <!--设置mybatis输出日志,打印在控制台-->
        <setting name="logImpl" value="STDOUT_LOGGING" />
    </settings>

    <!-- 取别名 -->
    <typeAliases>
        <typeAlias type="com.atSchool.Pojo.HouseUser" alias="HouseUser"/>
    </typeAliases>

    <!-- 配置环境 -->
    <!-- 环境集合属性对象,default:指定你需要使用哪一个环境子属性对象 -->
    <environments default="development">

        <!-- 环境子属性对象 -->
        <environment id="development">

            <!-- mybatis的事务类型, type:JDBC表示使用jdbc中的Connection对象的commit,rollback做事务处理 -->
            <transactionManager type="JDBC" />

            <!-- 数据源,type:POOLED表示使用连接池 -->
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver" />

                <!-- 这里不能用&而要用&amp; -->
                <property name="url" value="jdbc:mysql://127.0.0.1:3306/house?serverTimezone=UTC&amp;characterEncoding=UTF-8" />

                <property name="username" value="root" />
                <property name="password" value="password" />
            </dataSource>
        </environment>
    </environments>

    <!-- 映射器,指定sql映射文件位置 -->
    <mappers>
        <mapper resource="mapper_house.xml" />
    </mappers>
</configuration>

mapper.xml

这里可以不用取名为mapper.xml

sql映射文件,文件中配置了操作数据库的sql语句。<mark>此文件需要在 SqlMapConfig.xml中加载.</mark>

示例:mapper_book.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!--指定约束文件:限制、检查在当前文件中出现的标签、属性必须完全符合mybatis的要求-->
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<!-- namespace:命名空间,用于隔离sql语句。可以自定义,但推荐使用dao接口的全限定名称 -->
<mapper namespace="com.atSchool.Dao.UserDao">
    <!-- id:sql语句的唯一标识,mybatis会根据id找到要执行的sql语句,可自定义,建议使用dao接口中的方法名 resultType参数:说明sql语句执行后返回的数据类型,可以写全限定名称, 也可以在SqlMapConfig文件中取别名后使用 parameterType参数:说明输入类型(有占位符的时候需要指明),可以写全限定名称, 也可以在SqlMapConfig文件中取别名后使用 -->
    <select id="queryUser" resultType="HouseUser">
        select * from sys_user
    </select>

    <insert id="insertUser" parameterType="HouseUser">
        <!-- 这里占位符是#{},实际上他会调用表对应的pojo类中的get方法,所以pojo类中的属性名一定要和表中的一样 -->
        INSERT INTO `sys_user` (uName,uPassWord) VALUES (#{uName},#{uPassWord})
    </insert>

    <update id="updateUser" parameterType="HouseUser">
        UPDATE sys_user SET uName=#{uName},uPassWord=#{uPassWord}
        WHERE UID=#{UID}
    </update>

    <!-- 删除时只需要用到UID所以这里不要将类型设置为HouseUser,sql语句中占位符也不强制需求写的和表中的字段名一样 -->
    <delete id="deleteUser" parameterType="int">
        DELETE FROM sys_user WHERE UID=#{id}
    </delete>
</mapper>

db.properties

SqlMapConfig.xml文件中配置的数据库信息是静态的,如果我们将项目打包后,则很难进行修改,所以我们最好是配置一个动态的信息。就和JDBC中的使用配置文件连接数据库是一样的。

设置步骤:

  1. 在resource下创建db.properties文件

    className=com.mysql.cj.jdbc.Driver
    url=jdbc:mysql://127.0.0.1:3306/house?serverTimezone=UTC&characterEncoding=UTF-8
    username=root
    password=password
    
  2. SqlMapConfig.xml中配置

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
    
    <!-- configuration中可以有很多的标签,他们的顺序是固定的,不能随意更改 -->
    <configuration>
        <!-- 使用properties配置文件 -->
    	<properties resource="db.properties" />
    	
    	<!-- 配置环境 -->
    	<environments default="development">
    		<environment id="development">
    			<transactionManager type="JDBC" />
    			<dataSource type="POOLED">
    				<property name="driver" value="${className}" />
    				<property name="url" value="${url}" />
    				<property name="username" value="${username}" />
    				<property name="password" value="${password}" />
    			</dataSource>
    		</environment>
    	</environments>
    </configuration>
    

3.2.5 测试开始(增删改查)

测试步骤

  1. 创建SqlSessionFactoryBuilder对象

  2. 加载SqlMapConfig.xml配置文件

    (因为SqlMapConfig文件中已经制定了mapper文件,所以只需要读取它就可以了)

  3. 创建SqlSessionFactory对象

  4. 创建SqlSession对象

  5. 执行SqlSession对象执行sql语句,获取结果

  6. 提交(该步骤可以省略

    (mybatis默认不是自动提交事务的,所以在增删改后需要手动提交。

    该步骤也可以省略,前提是获取SqlSession对象时使用的是openSession(true),具体看3.3.2

  7. 释放资源

/MavenProject/src/test/java文件夹下创建包,再创建文件写代码:(一种规范)

Tools.java 工具类

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import java.io.IOException;
import java.io.InputStream;

public class Tools {
   
    /** * SqlSessionFactory的最佳使用范围是整个应用运行期间,一旦创建后可以重复使用. * 通常以单例模式管理 SqlSessionFactory. * 所以这里定义为静态全局常量,保证唯一性。 */
    private static final SqlSessionFactory build;
    // 这里没有设置为null,是因为常量一旦被赋值就不能修改

    static {
   
        // 1. 创建 SqlSessionFactoryBuilder 对象
        SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
        // 2. 加载 SqlMapConfig.xml 配置文件
        InputStream resourceAsStream = null;
        try {
   
            resourceAsStream = Resources.getResourceAsStream("SqlMapConfig.xml");
        } catch (IOException e) {
   
            e.printStackTrace();
        }
        // 3. 创建 SqlSessionFactory 对象
        build = sqlSessionFactoryBuilder.build(resourceAsStream);
    }

    /** * 获取SqlSession对象 * * @return 返回一个SqlSession对象 */
    public static SqlSession getSqlSession() {
   
        // 4. 创建 SqlSession 对象
        SqlSession sqlSession = null;
        if (build != null) {
   
            sqlSession = build.openSession();
        }
        return sqlSession;
    }
}
import java.util.List;
import com.atSchool.Pojo.HouseUser;

public class TestDemo {
   
	public static void main(String[] args) throws IOException {
   
		queryUser();
	}

	// 查询所有的用户信息
	public static void queryUser() {
   
        // 4. 创建`SqlSession`对象
		SqlSession openSession = Tools.getSqlSession();

		// 5. 执行`SqlSession`对象执行查询,获取结果User。
        // 传入的参数是mapper.xml配置文件中sql语句的id
        // 也可以是 mapper 文件中 mapper 标签中的namespace+id
		List<HouseUser> selectList = openSession.selectList("queryUser");

		// 6. 打印结果
		for (HouseUser houseUser : selectList) {
   
			System.out.println(houseUser);
		}

		// 7. 释放资源
		openSession.close();
	}
}

import java.util.List;
import com.atSchool.Pojo.HouseUser;

public class TestDemo {
   
	public static void main(String[] args) throws IOException {
   
		addUser();
	}

	// 添加user
	public static void addUser() {
   
		// 4. 创建`SqlSession`对象
		SqlSession openSession = Tools.getSqlSession();

		/** * 5. 执行`SqlSession`对象执行插入 * * int insert(String statement, Object parameter); * 使用给定的参数对象执行insert语句。任何生成的自动增量值或选择键条目都将修改给定的参数对象属性。将只返回受影响的行数。 * 参数: * statement:与要执行的语句匹配的唯一标识符,sql ID。 * parameter:要传递给语句的参数对象。 */
		int insert = openSession.insert("insertUser", new HouseUser("Maria", "123456"));
		System.out.println("受影响的行数为:" + insert);

		// 6. 提交
		openSession.commit();

		// 7. 释放资源
		openSession.close();
	}
}

import java.util.List;
import com.atSchool.Pojo.HouseUser;

public class TestDemo {
   
	public static void main(String[] args) throws IOException {
   
		deleteUser();
	}

	// 删除用户
	public static void deleteUser() {
   
		// 4. 创建`SqlSession`对象
		SqlSession openSession = Tools.getSqlSession();

		// 5. 执行`SqlSession`对象执行修改,传入的是sql id和参数
		int delete = openSession.delete("deleteUser", 23);
		System.out.println("受影响的行数为:" + delete);

		// 6. 提交
		openSession.commit();

		// 7. 释放资源
		openSession.close();
	}
}

import java.util.List;
import com.atSchool.Pojo.HouseUser;

public class TestDemo {
   
	public static void main(String[] args) throws IOException {
   
		updateUser();
	}

	// 修改用户信息
	public static void updateUser() {
   
		// 4. 创建`SqlSession`对象
		SqlSession openSession = Tools.getSqlSession();

		// 5. 执行`SqlSession`对象执行修改
		int update = openSession.update("updateUser", new HouseUser(21, "Jane", "12345"));
		System.out.println("受影响的行数为:" + update);

		// 6. 提交
		openSession.commit();

		// 7. 释放资源
		openSession.close();
	}
}

3.3、SQL语句中的参数

3.3.1、一个简单参数

简单参数:Java基本类型和String类型

使用 #{} 或者 @Param。

<select id="selectById" resultType="com.bjpowernode.domain.Student">
    select id,name,email,age from student where id=#{studentId}
</select>

<!--注意-->
#{studentId} , studentId 是自定义的变量名称,和方法参数名无关。

3.3.2、多个参数

1、使用@Param(常用)

思想:当需要使用多个参数时,通过类似取别名的概念进行参数标识。

使用:

  1. 接口中

    List<Student> selectStudent( @Param(“personName”) String name ) {
         }
    
  2. mapper文件中

    <select id="selectById" resultType="com.bjpowernode.domain.Student">
        select * from student where name = #{ personName }
    </select>
    

2、使用对象(主要使用)

思想:使用自定义类(表对应的实体类)中的属性,在resultType中本身就会使用到表对应的实体类,这里使用会更加的直观。

使用:

  1. 接口中

    List<student> selectMultiObject(QueryParam param);
    
  2. 完整的使用语法(太繁杂我们一般不用):

    语法:#{属性名, javaType=类型名称 ,jdbcType=数据类型}
    参数:javaType:Java中的数据类型 ,jdbcType:数据库中的数据类型
    
    <select id="selectMultiobject" resultType="com.bjpowernode.domain.Student">
    	select id, name, email, age from student
        where name=#{paramName, javaType=java.lang.String, jdbcType=VARCHAR}
    	or age=#{paramAge,javaType=java.lang.Integer, jdbcType=INTEGER}
    </select>
    

    简化后的语法:

    mybatis会通过反射获取javaType和jdbcType的值,不需要我们提供。

    语法:#{
         属性名}
    
    <select id="selectMultiobject" resultType="com.bjpowernode.domain.Student">
    	select id, name, email, age from student
        where name=#{
         paramName} or age=#{
         paramAge}
    </select>
    

3、使用位置(了解)

思想:按照sql语句中参数的位置从左往右依次获取对应的值。

使用:

  1. 接口中

    List<student> selectMultiObject(String name,int age);
    
  2. mapper中

    mybatis版本 <= 3.3,使用#{0}、#{1}....
    mybatis版本 >= 3.4,使用#{arg0}、#{arg1}....
        
    <select id="selectMultiobject" resultType="com.bjpowernode.domain.Student">
    	select id, name, email, age from student
        where name=#{arg0} or age=#{arg1}
    </select>
    

缺点:

按位置来,如果参数变化,或者位置写错,都会导致运行出错,不直观、方便,一般我们不使用。

4、使用Map(了解)

使用:

  1. 接口中

    List<Student> selectMultiByMap(Mapkstring,object> map);
    
  2. mapper中

    #{key值}
    
    <select id="selectMultiByMap" resultType="com.bjpowernode.domain.Student">
    	select id, name, email, age from student
        where name=#{paramName} or age=#{paramAge}
    </select>
    
  3. 使用代码里面

    Map<String,Object> map = new HashMap<>;
    map.put("paramName":"张三");
    map.put("paramAge":19");
    

3.4、使用小结

3.4.1、mybatis配置/架构说明

1、MyBatis架构

2、说明

配置文件说明
主要配置文件 说明
SqlMapConfig.xml 此文件作为mybatis的全局配置文件,配置了mybatis的运行环境等信息.
mapper.xml 即sql映射文件,文件中配置了操作数据库的sql语句。此文件需要在 SqlMapConfig.xml中加载.

SqlMapConfig.xml中配置的内容和顺序:

  • properties(属性)
  • settings(全局配置参数)
  • typeAliases(类型别名)
  • typeHandlers(类型处理器)
  • objectFactory(对象工厂)
  • plugins(插件)
  • environments(环境集合属性对象)
  • environment(环境子属性对象)
  • transactionManager(事务管理)
  • dataSource(数据源)
  • mappers(映射器)
架构说明
架构 说明
SqlSessionFactory 通过mybatis环境等配置信息构造SqlSessionFactory(即会话工厂),由会话工厂创建sqlSession即会话,操作数据库需要通过sqlSession进行.
Executor mybatis底层自定义了Executor执行器接口操作数据库,Executor接口有两个实现,一个是基本执行器、一个是缓存执行器.
Mapped Statement 1. Mapped Statement也是mybatis一个底层封装对象,它包装了mybatis配置信息及sql映射信息等.
mapper.xml文件中一个sql对应一个Mapped Statement对象,sql的id即是Mapped statement的id.
2. Mapped Statement对sql执行输入参数进行定义,包括HashMap、基本类型、pojo(持久化类),Executor通过MappedStatement在执行sql前将输入的java对象映射至sql中,<mark>输入参数映射</mark>就是jdbc编程中对preparedStatement设置参数.
3. Mapped Statement对sql执行输出结果进行定义,包括HashMap、基本类型、pojo, Executor通过MappedStatement在执行sql后将输出结果映射至java对象中,<mark>输出结果映射过程</mark>相当于jdbc编程中对结果的解析处理过程.

3.4.2、使用到的各个类、接口说明

类/接口 说明 备注
SqlSessionFactoryBuilder 类 1、SqlSessionFactoryBuilder用于创建SqlSessionFacoty。
2、SqlSessionFacoty一旦创建完成就不需要SqlSessionFactoryBuilder了,因为SqlSession是通过SqlSessionFactory创建的。所以可以将 SqlSessionFactoryBuilder当成一个工具类使用,最佳使用范围是方法范围即方法体内局部变量
Resouce类 mybatis中的一个类,负责读取主配置文件
SqlSessionFactory 接口 用于获取SqlSession对象。SqlSessionFactory定义了openSession的不同重载方法,SqlSessionFactory的最佳使用范围是整个应用运行期间,一旦创建后可以重复使用,通常以单例模式管理 SqlSessionFactory. 1、openSession():无参数的,获取的是非自动提交事务的SqlSession对象
2、openSession(boolean):根据参数获取SqlSession对象;true:获取自动提交的;false:获取的是非自动提交的
SqlSession 接口 1、SqlSession中封装了对数据库的操作,如:查询、插入、更新、删除等。
2、SqlSession通过SqlSessionFactory创建。
3、SqlSessionFactory是通过SqlSessionFactoryBuilder进行创建。
SqlSession对象不是线程安全的,需要在方法内部使用。在执行sql语句之前,使用openSession()获取SqlSession对象;在执行sql语句之后,需要close()关闭。这样才能保证它是安全的。

3.4.3、parameterType和resultType

说明 备注
parameterType 指定输入参数类型,mybatis通过ognl从输入对象中获取参数值拼接在sql中。值为Java中的数据类型全限定名称或者是mybatis定义的别名。 该参数不是强制的,mybatis通过反射机制能够发现接口参数的数据类型,<mark>所以一般我们不写</mark>
resultType 指定输出结果类型,mybatis将sql查询结果的一行记录数据映射为resultType指定类型的对象。如果有多条数据,则分别进行映射,并把对象放到容器List中 <mark>一般我们在SqlMapConfig文件使用typeAliases标签对整个包取别名,然后对resultType赋值</mark>

MyBatis支持的别名别名:

别名 Java中的数据类型
_byte byte
_long long
_short short
_int int
_integer int
_double double
_float float
_boolean boolean
string String
byte Byte
long Long
short Short
int Integer
integer Integer
double Double
float Float
boolean Boolean
date Date
decimal BigDecimal
bigdecimal BigDecimal
map Map

3.4.4、typeAliases(类型别名)

Mapper.xml文件中我们有时候会给类或者包取别名

<!-- 这是对一个类取别名 -->
<typeAliases>
    <typeAlias type="com.atSchool.Pojo.HouseUser" alias="HouseUser" />
</typeAliases>

<!-- 这是对一个包中的所有类取别名,默认所有的类名都会是原来类名的小写,但是你也可以用原来的类名 -->
<typeAliases>
    <package name="com.atSchool.Pojo"/>
</typeAliases>

3.4.5、占位符#{} 和 ${}(面试会问)

#{}

#{}表示一个占位符号,就和JDBC中sql语句中的一样

  1. 通过#{}可以实现preparedStatement向占位符中设置值,自动进行java类型和jdbc类型转换.

  2. #{}可以接收简单类型值或pojo属性值。
    如果 parameterType传输单个简单类型值,#{}括号中可以是value或其它名称

  3. #{}可以有效防止sql注入

${}

${}表示告诉mybatis使用 包 含 的 字 符 串 替 换 所 在 位 置 。 使 用 S t a t e m e n t 把 s q l 语 句 包含的字符串替换所在位置。使用Statement把sql语句 使Statementsql{}的内容连接起来。主要用在替换表名,列名,不同列排序等操作。

  1. 通过${}可以将parameterType传入的内容拼接在sql中且不进行jdbc类型转换
  2. ${}可以接收简单类型值或pojo属性值。
    如果parameterType传输单个简单类型值(例如 String 类型),${}括号中只能是 value

区别

  1. 日志中使用#时sql语句的结果:select * from employees where employeeNumber=?

    日志中使用$时sql语句的结果:select * from employees where employeeNumber=1002

  2. $使用Statement对象执行sql,效率比#使用PrepareStatement对象执行sql要低。

  3. #更安全,使用 存 在 安 全 性 问 题 , 因 为 存在安全性问题,因为 只是单纯的字符串拼接,所以当我们传入的值为:1002;drop table employees时,sql语句会变成:select * from employees where employeeNumber=1002;drop table employees,这样就是两句sql语句了,会存在一定的安全隐患。

所以我们一般不用 $ ,要使用通常都只是用它来替换表名或列名。

3.4.6、selectOne和selectList

说明
selectOne 查询一条记录
如果使用selectOne查询多条记录则抛出异常:org.apache.ibatis.exceptions.TooManyResultsException: Expected one result (or null) to be returned by selectOne(), but found: 3 at org.apache.ibatis.session.defaults.DefaultSqlSession.selectOne(DefaultSqlSession.java:70)
selectList 可以查询一条或多条记录

04. Mapper动态代理方式开发

4.1、原始Dao开发的流程

  1. 创建Dao接口
  2. 实现Dao接口:在里面使用MyBatis访问数据库
  3. 测试/使用 类:调用Dao接口实现类中的方法使用

4.2、原始Dao开发中存在的问题

  • Dao方法体存在重复代码:通过SqlSessionFactory创建SqlSession,调用SqlSession的数据库操作方法
  • 调用sqlSession的数据库操作方法需要指定statement的id,这里存在硬编码,不利于开发维护

4.3、Mapper动态代理开发规范

Mapper接口开发方法只需要程序员编写Mapper接口(相当于Dao接口),由Mybatis框架根据接口定义创建接口的动态代理对象,代理对象的方法体同上边Dao接口实现类方法. Mapper接口开发需要遵循以下规范:

  1. Mapper.xml文件中的namespace属性的值要与Dao接口的类路径相同.

  2. Dao接口方法名和Mapper.xml中定义的每个Content Model(内容模型)的id相同

  3. Dao接口方法的输入参数类型和mapper.xml中定义的每个sql的parameterType的类型相同

  4. Dao接口方法的输出参数类型和mapper.xml中定义的每个sql的resultType的类型相同

4.4、动态代理开发实战

1、实体类

/** * house数据库中sys_user表的实体类 */
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class HouseUser {
   
	private Integer UID; // 这里必须和表中的列名一样
	private String uName;
	private String uPassWord;
}

2、mapper配置文件

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<!-- namespace:命名空间,用于隔离sql语句 -->
<mapper namespace="com.atSchool.Dao.UserDao">
	<select id="queryUser" resultType="HouseUser" parameterType="int">
		select * from sys_user
	</select>

	<select id="queryUserById" resultType="HouseUser" parameterType="int">
		select * from sys_user WHERE UID=#{uid}
	</select>

	<insert id="insertUser" parameterType="HouseUser">
		<!-- keyColumn:说明是数据库中的哪一列 keyProperty:pojo中对应的字段名是什么 order:添加后还是添加前返回 resultType:返回的数据类型是什么 -->
		<selectKey keyColumn="UID" keyProperty="UID" order="AFTER" resultType="int">
			SELECT LAST_INSERT_ID()
		</selectKey>
		<!-- 这里占位符是#{},实际上他会调用表对应的pojo类中的get方法,所以pojo类中的属性名一定要和表中的一样 -->
		INSERT INTO `sys_user` (uName,uPassWord) VALUES (#{uName},#{uPassWord})
	</insert>

	<update id="updateUser" parameterType="HouseUser">
		UPDATE sys_user SET uName=#{uName},uPassWord=#{uPassWord}
		WHERE UID=#{UID}
	</update>

	<!-- 删除时只需要用到UID所以这里不要将类型设置为HouseUser,sql语句中占位符也不强制需求写的和表中的字段名一样 -->
	<delete id="deleteUser" parameterType="int">
		DELETE FROM sys_user WHERE UID=#{id}
	</delete>
</mapper>

3、Dao接口

package com.atSchool.Dao;

import java.util.List;

import com.atSchool.Pojo.HouseUser;

public interface UserDao {
   
	List<HouseUser> queryUser();	// 对应着配置文件中标签id为queryUser的sql语句
	
	HouseUser queryUserById(int id);	// 对应着配置文件中标签id为queryUserById的sql语句
	
	void insertUser(HouseUser houseUser);
	
	void updateUser(HouseUser houseUser);
	
	void deleteUser(int id);
}

4、测试类

使用SqlSession类中的getMapper方法,实现动态代理。

package com.atSchool.test;

import java.io.IOException;
import java.io.InputStream;
import java.util.List;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import com.atSchool.Dao.UserDao;
import com.atSchool.Pojo.HouseUser;

public class TestDao {
   
	public static void main(String[] args) throws IOException {
   
		InputStream resourceAsStream = Resources.getResourceAsStream("SqlMapConfig.xml");
		SqlSession openSession = new SqlSessionFactoryBuilder().build(resourceAsStream).openSession();
		/** * 使用mybatis的动态代理机制,使用Sqlsession.getMapper(dao接口) * getMapper能获取dao接口对于的实现类对象。 */
        UserDao mapper = openSession.getMapper(UserDao.class);
		HouseUser queryUser = mapper.queryUserById(10);
		System.out.println(queryUser);
		
		List<HouseUser> queryUser2 = mapper.queryUser();
		for (HouseUser houseUser : queryUser2) {
   
			System.out.println(houseUser);
		}
	}
}

05. 封装MyBatis输出结果

5.1、resultType

resultType指定sql语句执行完毕的输出结果类型,sql语句执行完毕后,数据转为Java对象。

返回的类型可以是:

  1. 简单类型

  2. 对象类型

  3. Map:其中Map的key是表的列名,Map的value是表的列值

    返回结果类型为Map时,结果只能有一条,如果是多条结果则会报错。

5.2、resultMap

resultType可以指定将查询结果映射为pojo,但<mark>需要pojo的属性名和sql查询的列名一致</mark>方可映射成功。

resultMap可以自定义表列名和Java类中属性的对应关系。即如果sql查询字段名和pojo类的的属性名不一致,可以通过resultMap将字段名和属性名作一个对应关系,resultMap实质上还需要将查询结果集映射到pojo对象中。 resultMap可以实现将查询结果映射为复杂类型的pojo,比如在查询结果映射对象中包括pojo和list实现一对一查询和一对多查询。

使用

  1. 在mapper文件中定义

    <!-- 定义resultMap id:自定义名称,标识该resultMap type:数据类型的全限定名称 -->
    <resultMap id="test" type="">
        <!-- 定义列名和Java类中属性的关系 -->
        <!-- 主键列,使用id标签。column:列名,property:Java类的属性名 -->
        <id column="" property=""/>
        <!-- 非主键列,使用result标签 -->
        <result column="" property=""/>
    </resultMap>
    
  2. 在mapper文件中的sql标签中引用

    <select id="" resultMap="test">
    </select>
    

5.3、resultType和resultMap的区别

resultType

  • 查询出的字段在相应的pojo中必须有和它相同的字段对应,或者基本数据类型

  • 适合简单查询

resultMap

  • 需要自定义字段,或者多表查询,一对多等关系,比resultType更强大

  • 适合复杂查询

5.4、pojo类中属性与表列名不一致处理方式

  1. 使用resultMap
  2. 使用resultType + sql中的取别名

06. QueryVo

开发中通过可以使用pojo传递查询条件.

但是查询条件可能是综合的查询条件,不仅包括用户查询条件还包括其它的查询条件(比如查询用户信息的时候,将用户购买商品信息也作为查询条件),这时可以使用包装对象传递输入参数.

包装对象:Pojo类中的一个属性是另外一个pojo.

需求:根据用户名模糊查询用户信息,查询条件放到QueryVo的user属性中(vo:viewObject)。

QueryVo类

package com.atSchool.Pojo;

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class QueryVo {
   
	private HouseUser houseUser;
}

mapper映射文件中

<select id="queryUserByName" resultType="HouseUser" parameterType="QueryVo">
    select * from sys_user WHERE uName like "%${houseUser.uName}%"
</select>

UserDao(dao接口)中

List<HouseUser> queryUserByName(QueryVo queryVo);

测试类

package com.atSchool.test;

import java.io.IOException;
import java.io.InputStream;
import java.util.List;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import com.atSchool.Dao.UserDao;
import com.atSchool.Pojo.HouseUser;
import com.atSchool.Pojo.QueryVo;

public class TestDao {
   
	public static void main(String[] args) throws IOException {
   
		InputStream resourceAsStream = Resources.getResourceAsStream("SqlMapConfig.xml");
		SqlSession openSession = new SqlSessionFactoryBuilder().build(resourceAsStream).openSession();
		UserDao mapper = openSession.getMapper(UserDao.class);

		HouseUser houseUser = new HouseUser();
		houseUser.setuName("张");
		List<HouseUser> queryUserByName = mapper.queryUserByName(new QueryVo(houseUser));
		for (HouseUser houseUser2 : queryUserByName) {
   
			System.out.println(houseUser2);
		}
	}
}

07. 动态SQL

7.1、简介

通过mybatis提供的各种标签方法实现动态拼接sql。

例如下面的语句,如果like后面的"%张%"查询条件是空的,还是能够查询出来的;但是如果前面的UID<10查询条件是空的话,则不能查询到结果,所以我们这里需要使用到动态SQL。

SELECT * FROM `sys_user` WHERE UID<10 AND uName LIKE "%张%"

7.2、基础使用

动态SQL常用标签 说明
<where></where> where(主要是用来简化SQL语句中where条件判断的,能智能的处理and or,不必担心多余导致语法错误)
<if test="判断条件">sql片段</if> if语句(简单的条件判断)
<sql> sql片段 </sql> 定义一个sql语句,一般和include标签搭配使用
<include refid="sql语句id" /> include(将重复的sql语句提取出来,配合mapper.xml中的sql标签使用)
<foreach collection="遍历的集合" item="遍历出来的元素" open="在前面需要添加的sql片段" close="在结尾需要添加的sql片段" separator="分隔符"> #{调用遍历出来的元素} </foreach> foreach(在实现MyBatis in语句查询时特别有用)

使用动态sql后:

<select id="queryDynamic" resultMap="HouserUserMapping" resultType="HouseUser" parameterType="QueryVo">
    SELECT * FROM `sys_user`
    <where>
        <if test="houseUser.id!=null and houseUser.id!=''">
            AND UID #{houseUser.id}
        </if>
        <if test="houseUser.name!=null and houseUser.name!=''">
            AND uName LIKE "%${houseUser.name}%"
        </if>
    </where>
</select>

7.3、使用include提取SQL语句中相同的部分

<sql id="commonSql">
    select * from sys_user
</sql>

<select id="queryUser" resultMap="HouserUserMapping" resultType="houseuser" parameterType="int">
    <include refid="commonSql" />
</select>

<select id="queryUserById" resultMap="HouserUserMapping" resultType="HouseUser" parameterType="int">
    <include refid="commonSql" />
    WHERE UID=#{uid}
</select>

7.4、使用foreach标签

需求:

  • 之前我们都是以传参/动态sql的方式对sql语句进行处理,但是如果遇到了IN的查询条件,就只能使用foreach进行处理了。例如以下语句:

    SELECT * FROM `sys_user` WHERE UID IN (1,3,4,11)
    

1、在QueryVo中添加一个集合,用于存储sql语句IN后面的条件值

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class QueryVo {
   
	private HouseUser houseUser;
	private List<Integer> ids; // sql语句中有in条件时用到
}

2、在mapper.xml文件中添加

<!-- type属性:说明需要映射的pojo类 id属性:区分resultMap标签的 -->
<resultMap type="HouseUser" id="HouserUserMapping">
    <!-- 如果想要设置表中的id字段就必须使用 id标签 -->
    <!-- colomn属性:对应查询结果中的字段名 -->
    <!-- property:对应pojo类中的属性名 -->
    <id column="UID" property="id" />
    <result column="uName" property="name" />
</resultMap>

<select id="queryUserByIds" resultMap="HouserUserMapping" resultType="HouseUser" parameterType="QueryVo">
    SELECT * FROM `sys_user`
    WHERE UID
    IN
    <!--collection属性:遍历的集合,这里是Queryvo的ids属性 -->
    <!--item属性:遍历的项目,可以随便写,但是和后面的#{}里面要一致 -->
    <!--open:在前面添加的sqL片段 -->
    <!--close属性:在结尾处添加的sqL片段 -->
    <!--separator属性:指定遍历的元素之间使用的分隔符 -->
    <foreach collection="ids" item="item" open="(" close=")" separator=",">
        #{item}
    </foreach>
</select>

3、在UserDao接口中添加相应的方法(动态代理)

// 根据多个id查询
List<HouseUser> queryUserByIds(QueryVo queryVo);

3、开始测试

package com.atSchool.test;

import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.List;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import com.atSchool.Dao.UserDao;
import com.atSchool.Pojo.HouseUser;
import com.atSchool.Pojo.QueryVo;

public class TestDao {
   
	public static void main(String[] args) throws IOException {
   
		InputStream resourceAsStream = Resources.getResourceAsStream("SqlMapConfig.xml");
		SqlSession openSession = new SqlSessionFactoryBuilder().build(resourceAsStream).openSession();
		UserDao mapper = openSession.getMapper(UserDao.class);

		List<Integer> asList = Arrays.asList(1, 2, 3, 11);
		List<HouseUser> queryUserByIds = mapper.queryUserByIds(new QueryVo(null, asList));
		for (HouseUser houseUser : queryUserByIds) {
   
			System.out.println(houseUser);
		}
	}
}

08. MySQL自增主键返回

需求

  • 由于有些表的id字段是自增的,所以当添加一条新的数据后可能会需要知道这个新增数据的自增字段的值是多少。

SQL语句

  • SELECT LAST_INSERT_ID()

实现

<insert id="insertUser" parameterType="HouseUser">
    <!-- keyColumn:说明是数据库中的哪一列 keyProperty:pojo中对应的字段名是什么 order:添加后还是添加前返回 resultType:返回的数据类型是什么 -->
    <selectKey keyColumn="UID" keyProperty="UID" order="AFTER" resultType="int">
        SELECT LAST_INSERT_ID()
    </selectKey>
    INSERT INTO `sys_user` (uName,uPassWord) VALUES (#{uName},#{uPassWord})
</insert>
/** * 添加user并返回主键 */
public static void addUser() {
   
    // 4. 创建`SqlSession`对象
    SqlSession openSession = Tools.getSqlSessionion();

    // 5. 执行`SqlSession`对象执行插入
    HouseUser houseUser = new HouseUser("Maria", "123456");
    int insert = openSession.insert("insertUser", houseUser);
    System.out.println("受影响的行数为:" + insert);

    // 6. 提交
    openSession.commit();

    /** * 在添加数据并提交后,主键会自动的添加到前面创建的实体类的对象中,打印该对象就可以看见了 */
    System.out.println(houseUser);
    
    // 7. 释放资源
    openSession.close();
}

09. 控制台打印日志

1、使用mybatis自带的

在SqlMapConfig配置文件中加入

<settings>
    <!--设置mybatis输出日志,打印在控制台-->
    <setting name="logImpl" value="STDOUT_LOGGING" />
</settings>

2、Log4j的使用

日志是应用软件中不可缺少的部分,Apache的开源项目log4j是一个功能强大的日志组件,提供方便的日志记录。

<mark>使用时需要添加一个log4j.properties文件。就和学习Hadoop时一样</mark>

log4j.rootLogger=debug, stdout, R
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
# Pattern to output the caller's file name and line number.
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] (%F:%L) - %m%n
log4j.appender.R=org.apache.log4j.RollingFileAppender
log4j.appender.R.File=./log/Gosion.log
log4j.appender.R.MaxFileSize=100KB
# Keep one backup file
log4j.appender.R.MaxBackupIndex=5
log4j.appender.R.layout=org.apache.log4j.PatternLayout
log4j.appender.R.layout.ConversionPattern=%p %t %c - %m%n

10、SqlMapConfig文件中一次性指定多个mapper

之前在SqlMapConfig文件中指定mapper都是一个一个指定的,但其实也可以一次性指定。

之前:

<mappers>
    <mapper resource="mapper_house.xml" />
</mappers>

之后:

<mappers>
    <package name="com.study.dao" />
</mappers>

使用该方式的前提条件:

  1. mapper文件所在位置必须和接口在同一包下。
  2. mapper文件的名字必须和接口的名字一样。
  3. <mark>maven默认只识别resource中的资源文件,所以要使用该方式还得在SqlMapConfig文件中使用资源插件告诉maven,mapper文件所在包中的文件也要识别</mark>。

11. 关联查询

表说明:

  • user表

  • orders表

表对应的pojo类:

  package com.atSchool.Pojo;
  
  import lombok.Getter;
  import lombok.Setter;
  import lombok.ToString;
  
  /** * user表的pojo类 */
  @Getter
  @Setter
  @NoArgsConstructor
  @AllArgsConstructor
  @ToString
  public class User {
   
  	private Integer id;
  	private String username;
  	private String birthday;
  	private String sex;
  	private String address;
  }
  package com.atSchool.Pojo;
  
  import lombok.Getter;
  import lombok.Setter;
  import lombok.ToString;
  
  /** * order表的pojo类 */
  @Getter
  @Setter
  @NoArgsConstructor
  @AllArgsConstructor
  @ToString
  public class Order {
   
  	private Integer id;
  	private String userId;
  	private String number;
  	private String creatTime;
  	private String note;
  }

11.1 一对一查询

写在前面:如果数据库换了的话要记得在配置文件中修改连接到的数据库。

一对一查询:例如订单关联用户,是一对一的关系。

SELECT o.id,o.user_id,o.number,o.createtime,o.note,u.username,u.birthday,u.sex,u.address
FROM `orders` o LEFT JOIN `user` u ON u.id=o.user_id

11.1.1 实现方式一:使用ResultType

思路:

  • 定义专门的pojo类作为输出类型,其中定义了sql查询结果集所有的字段。此方法较为简单,企业中使用普遍。

实现:

1、根据查询结果创建一个pojo类,用来存储返回的数据

package com.atSchool.Pojo;

import lombok.Getter;
import lombok.Setter;

/** * 多表查询时的类 * order left join user */
@Getter
@Setter
public class OrderUser extends Order {
   
	private String uName;
	private String birthday;
	private String sex;
	private String address;

	public OrderUser() {
   
	}

	public OrderUser(Integer id, Integer userId, String number, String creatTime, String note, String uName,
			String birthday, String sex, String address) {
   
		super(id, userId, number, creatTime, note);
		this.uName = uName;
		this.birthday = birthday;
		this.sex = sex;
		this.address = address;
	}

	// 这里还需要将父类的信息加上,否则,遍历的时候,数据会不全
	@Override
	public String toString() {
   
		return "OrderUser [uName=" + uName + ", birthday=" + birthday + ", sex=" + sex + ", address=" + address
				+ ", toString()=" + super.toString() + "]";
	}
}

2、创建一个mapper

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<!-- namespace:命名空间,用于隔离sql语句 -->
<mapper namespace="com.atSchool.Dao.OrderUserDao">
	<!-- type属性:说明需要映射的pojo类 id属性:区分resultMap标签的 -->
	<resultMap type="OrderUser" id="OrderUserMapping">
		<!-- 如果想要设置表中的id字段就必须使用 id标签 -->
		<!-- colomn属性:对应查询结果中的字段名 -->
		<!-- property:对应pojo类中的属性名 -->
		<id column="id" property="id" />
		<result column="user_id" property="userId" />
		<result column="number" property="number" />
		<result column="createtime" property="creatTime" />
		<result column="note" property="note" />
		<result column="username" property="uName" />
		<result column="birthday" property="birthday" />
		<result column="sex" property="sex" />
		<result column="address" property="address" />
	</resultMap>

	<select id="queryOrderUser" resultMap="OrderUserMapping" resultType="OrderUser">
		SELECT o.id,o.user_id,o.number,o.createtime,o.note,u.username,u.birthday,u.sex,u.address
		FROM `orders` o LEFT JOIN `user` u ON u.id=o.user_id
	</select>
</mapper>

注意:

  1. 别忘了在SqlMapConfig.xml中添加相应的mapper
  2. 注意这里返回的类型,type中的类名都可以直接写类名,是因为在SqlMapConfig.xml中统一取别名了

3、编写Dao接口

package com.atSchool.Dao;

import java.util.List;

import com.atSchool.Pojo.OrderUser;

/** * 多表查询时的dao接口 * order left join user */
public interface OrderUserDao {
   
	List<OrderUser> queryOrderUser();
}

4、开始测试

// 测试OrderUser中的查询
public static void teseForOrderUser() throws IOException {
   
    InputStream resourceAsStream = Resources.getResourceAsStream("SqlMapConfig.xml");
    SqlSession openSession = new SqlSessionFactoryBuilder().build(resourceAsStream).openSession();
    OrderUserDao mapper = openSession.getMapper(OrderUserDao.class);
    List<OrderUser> queryOrderUser = mapper.queryOrderUser();
    for (OrderUser orderUser : queryOrderUser) {
   
        System.out.println(orderUser);
    }
}

11.1.2 实现方式二:使用ResultMap

思路:

  • 在Order类中加入User属性,user属性中用于存储关联查询的用户信息,因为订单关联查询用户是一对一关系,所以这里使用单个User对象存储关联查询的用户信息.

实现:

1、在Order类中加入User属性

package com.atSchool.Pojo;

import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

/** * order表的pojo类 */
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class Order {
   
	private User user;
	private Integer id;
	private Integer userId;
	private String number;
	private String creatTime;
	private String note;
}

2、使用resultMap标签中的association进行关联

<resultMap type="Order" id="OrderMapping">
    <!-- 如果想要设置表中的id字段就必须使用 id标签 -->
    <!-- colomn属性:对应查询结果中的字段名 -->
    <!-- property:对应pojo类中的属性名 -->
    <id column="id" property="id_inOrder" />
    <result column="user_id" property="userId" />
    <result column="number" property="number" />
    <result column="createtime" property="creatTime" />
    <result column="note" property="note" />

    <association property="user" javaType="User">
        <id column="id" property="id_inUser" />
        <result column="username" property="uName" />
        <result column="birthday" property="birthday" />
        <result column="sex" property="sex" />
        <result column="address" property="address" />
    </association>
</resultMap>

<select id="queryOrderUser2" resultMap="OrderMapping" resultType="Order">
    SELECT o.id,o.user_id,o.number,o.createtime,o.note,u.username,u.birthday,u.sex,u.address
    FROM `orders` o LEFT JOIN `user` u ON u.id=o.user_id
</select>

3、在Dao接口中添加相应的方法

List<Order> queryOrderUser2();

4、开始测试

public static void teseForOrderUser() throws IOException {
   
    InputStream resourceAsStream = Resources.getResourceAsStream("SqlMapConfig.xml");
    SqlSession openSession = new SqlSessionFactoryBuilder().build(resourceAsStream).openSession();
    OrderUserDao mapper = openSession.getMapper(OrderUserDao.class);
    List<Order> queryOrder = mapper.queryOrderUser2();
    for (Order orderUser : queryOrder) {
   
        System.out.println(orderUser);
    }
}

11.2 一对多查询

一对多查询:例如用户关联订单,是一对多的关系。

SELECT u.id,u.username,u.birthday,u.sex,u.address,o.number,o.createtime,o.note
FROM `user` u JOIN `orders` o ON u.id=o.user_id

1、在User中添加List<Order> orders属性

package com.atSchool.Pojo;

import java.util.List;

import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

/** * user表的pojo类 */
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class User {
   
	private Integer id_inUser;
	private String uName;
	private String birthday;
	private String sex;
	private String address;
	private List<Order> orders;
}

2、使用resultMap标签中的collection进行关联

<resultMap type="User" id="UserMapping">
    <!-- 如果想要设置表中的id字段就必须使用 id标签 -->
    <!-- colomn属性:对应查询结果中的字段名 -->
    <!-- property:对应pojo类中的属性名 -->
    <id column="id" property="id_inUser" />
    <result column="username" property="uName" />
    <result column="birthday" property="birthday" />
    <result column="sex" property="sex" />
    <result column="address" property="address" />

    <!-- property属性:指定pojo中需要关联的属性 -->
    <!-- javaType属性:关联的属性的类型 -->
    <!-- ofType属性:泛型 -->
    <collection property="orders" javaType="list" ofType="Order">
        <id column="id(2)" property="id_inOrder" />
        <result column="user_id" property="userId" />
        <result column="number" property="number" />
        <result column="createtime" property="creatTime" />
        <result column="note" property="note" />
    </collection>
</resultMap>

<select id="queryOrderUser3" resultMap="UserMapping">
    SELECT u.id,u.username,u.birthday,u.sex,u.address,o.id,o.number,o.createtime,o.note
    FROM `user` u JOIN `orders` o ON u.id=o.user_id
</select>
注意:这里有一个易错的经典错误
  • 错误:一对多查询,明明日志信息显示查询到的数据条数是对的,但是一打印,list中的数量总是只有一条!

  • 出错原因:因为返回的列没有用于区分权限的id,导致mybatis不知道如何区分,于是把每一条记录都映射成了一个对象。

  • 解决方法:

    在连接的两张表中将两张表的id全部查询出来。

    这里可能又会出现另外一个错误:当连接的两张表中的id字段名相同时,查询出来的结果中可能会出现如下情况:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    此时我们在resultMap中取别名时就得注意了,column属性的值就得是和查询结果一样的字段名,如我的为id(1)

3、在Dao接口中添加相应的方法

List<User> queryOrderUser3();

4、开始测试

public static void teseForOrderUser() throws IOException {
   
    InputStream resourceAsStream = Resources.getResourceAsStream("SqlMapConfig.xml");
    SqlSession openSession = new SqlSessionFactoryBuilder().build(resourceAsStream).openSession();
    OrderUserDao mapper = openSession.getMapper(OrderUserDao.class);
    List<User> queryOrderUser3 = mapper.queryOrderUser3();
    for (User user : queryOrderUser3) {
   
        System.out.println(user);
        for (Order order : user.getOrders()) {
   
            System.out.println(order);
        }
        System.out.println("=======================");
    }
}

11.3 嵌套查询

嵌套查询:例如员工表中除了老板,其中主管、小组组长、组员等都是员工。当我们想要查询该员工所有下级时这个时候就可以用嵌套查询。

11.3.1 表结构

11.3.2 需求

查询出某个人下的所有员工(递归查询)。

11.3.3 实现

1、创建pojo类

package com.atSchool.Pojo;

import java.util.List;

import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

/** * 员工表对应的pojo类 */
@Getter
@Setter
@ToString
public class Employees {
   
	private String id;	// 员工编号
	private String lastName;	// 姓
	private String firstName;	// 名
	private String reportsTo;	// 上级编号
	private List<Employees> emps;
}

2、创建Dao接口

package com.atSchool.Dao;

import java.util.List;

import com.atSchool.Pojo.Employees;

public interface EmployeesDao {
   
	List<Employees> queryReportToById(String id);
}

3、创建mapper_employees.xml资源文件

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<!-- namespace:命名空间,用于隔离sql语句 -->
<mapper namespace="com.atSchool.Dao.EmployeesDao">
	<resultMap type="Employees" id="EmployeesMapping">
		<!-- 如果想要设置表中的id字段就必须使用 id标签 -->
		<!-- colomn属性:对应查询结果中的字段名 -->
		<!-- property:对应pojo类中的属性名 -->
		<id column="employeeNumber" property="id" />
		<result column="lastName" property="lastName" />
		<result column="firstName" property="firstName" />
		<result column="reportsTo" property="reportsTo" />
		
		<collection property="emps" column="{id2=employeeNumber}" select="select2"/>
	</resultMap>

	<select id="queryReportToById" resultMap="EmployeesMapping">
		SELECT employeeNumber,lastName,firstName,reportsTo FROM `employees` 
		WHERE employeeNumber=#{id1}
	</select>
	
	<select id="select2" resultMap="EmployeesMapping">
		SELECT employeeNumber,lastName,firstName,reportsTo FROM `employees` 
		WHERE reportsTo=#{id2}
	</select>
</mapper>

解析:

  • collection中的参数 说明
    select 另一个映射查询的id,MyBatis会额外执行这个查询获取嵌套对象的结果。
    column 将主查询中列的结果作为嵌套查询的参数,配置方式如column="{prop1=col1,prop2=col2}",prop1和prop2将作为嵌套查询的参数。
    fetchType 数据加载方式,可选值为lazy和eager,分别为延迟加载和积极加载。
    如果要使用延迟加载,除了将fetchType设置为lazy,还需要注意全局配置aggressiveLazyLoading的值应该为false。这个参数在3.4.5版本之前默认值为ture,从3.4.5版本开始默认值改为false。
    MyBatis提供的lazyLoadTriggerMethods参数,支持在触发某方法时直接触发延迟加载属性的查询,如equals()方法。
  • 执行步骤:

    1. idqueryReportToByIdselect语句先执行,返回的数据存储在collection中映射的pojo对应的属性中(注意,此时还没用到collection);
    2. 紧接着执行collection关联的 idselect2的语句,将返回的数据全部存储到select2指定的resultMap中。此时其中的collection又会进一步执行与之关联的sql语句,直到全部查完为止。
  • 结果解析:

    • 结果应当是一个严格的树的形状,根是我们要查询下级的人的所有信息,其他部分即为不同等级的他的手下(存储在pojo类中的emps中)。
    • 更详细的解析请看测试中的结果说明。

4、在SqlMapConfig.xml文件中注册mapper

5、开始测试

public static void teseForEmployees() throws IOException {
   
    InputStream resourceAsStream = Resources.getResourceAsStream("SqlMapConfig.xml");
    SqlSession openSession = new SqlSessionFactoryBuilder().build(resourceAsStream).openSession();
    EmployeesDao mapper = openSession.getMapper(EmployeesDao.class);
    List<Employees> queryReportToById = mapper.queryReportToById("1056");

    Employees employees = queryReportToById.get(0);	// 整个queryReportToById中只有一个Employees对象,该对象中的emps中嵌套了很多的Employees对象
    List<Employees> emps = employees.getEmps();	// 获取所有的下级对象
    for (Employees employees2 : emps) {
   
        System.out.println(employees2);
    }
}

结果:

Employees(id=1088, lastName=Patterson, firstName=William, reportsTo=1056, emps=[Employees(id=1611, lastName=Fixter, firstName=Andy, reportsTo=1088, emps=[]), Employees(id=1612, lastName=Marsh, firstName=Peter, reportsTo=1088, emps=[]), Employees(id=1619, lastName=King, firstName=Tom, reportsTo=1088, emps=[])])
Employees(id=1102, lastName=Bondur, firstName=Gerard, reportsTo=1056, emps=[Employees(id=1337, lastName=Bondur, firstName=Loui, reportsTo=1102, emps=[]), Employees(id=1370, lastName=Hernandez, firstName=Gerard, reportsTo=1102, emps=[]), Employees(id=1401, lastName=Castillo, firstName=Pamela, reportsTo=1102, emps=[]), Employees(id=1501, lastName=Bott, firstName=Larry, reportsTo=1102, emps=[]), Employees(id=1504, lastName=Jones, firstName=Barry, reportsTo=1102, emps=[]), Employees(id=1702, lastName=Gerard, firstName=Martin, reportsTo=1102, emps=[])])
Employees(id=1143, lastName=Bow, firstName=Anthony, reportsTo=1056, emps=[Employees(id=1165, lastName=Jennings, firstName=Leslie, reportsTo=1143, emps=[]), Employees(id=1166, lastName=Thompson, firstName=Leslie, reportsTo=1143, emps=[]), Employees(id=1188, lastName=Firrelli, firstName=Julie, reportsTo=1143, emps=[]), Employees(id=1216, lastName=Patterson, firstName=Steve, reportsTo=1143, emps=[]), Employees(id=1286, lastName=Tseng, firstName=Foon Yue, reportsTo=1143, emps=[]), Employees(id=1323, lastName=Vanauf, firstName=George, reportsTo=1143, emps=[])])
Employees(id=1621, lastName=Nishi, firstName=Mami, reportsTo=1056, emps=[Employees(id=1625, lastName=Kato, firstName=Yoshimi, reportsTo=1621, emps=[])])

解析说明:

  • 这里没有打印根节点的信息,即这些都是id为1056的员工的直系下属,其中emps属性中存储的是这些下属的下属。

注意:如果数据库更改了的话就需要修改一下配置文件


12. pagehelper分页

首先要在pom.xml中配置PageHelper的依赖在https://mvnrepository.com/中可以发现 pagehelper有4.x和5.x两个版本,用法有所不同,并不是向下兼容,在使用5.x版本的时候可能会报错。这里我们使用4.2.1版本的。

1、导包

在maven仓库中查找,选择github的

<!-- https://mvnrepository.com/artifact/com.github.pagehelper/pagehelper -->
<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper</artifactId>
    <version>4.2.1</version>
</dependency>

2、在SqlMapConfig.xml文件中配置

<plugins>
    <!-- com.github.pagehelper为PageHelper类所在包名 -->
    <plugin interceptor="com.github.pagehelper.PageHelper">
        <property name="dialect" value="mysql" />
        
        <!-- 该参数默认为false -->
        <!-- 设置为true时,会将RowBounds第一个参数offset当成pageNum页码使用 -->
        <!-- 和startPage中的pageNum效果一样 -->
        <property name="offsetAsPageNum" value="false" />
        
        <!-- 该参数默认为false -->
        <!-- 设置为true时,使用RowBounds分页会进行count查询 -->
        <property name="rowBoundsWithCount" value="false" />
        
        <!-- 设置为true时,如果pageSize=0或者RowBounds.limit = 0就会查询出全部的结果 -->
        <!-- (相当于没有执行分页查询,但是返回结果仍然是Page类型) -->
        <property name="pageSizeZero" value="true" />
        
        <!-- 3.3.0版本可用 ‐ 分页参数合理化,默认false禁用 -->
        <!-- 启用合理化时,如果 pageNum<1 会查询第一页,如果 pageNum>pages 会查询最后一页 -->
        <!-- 禁用合理化时,如果 pageNum<1 或 pageNum>pages 会返回空数据 -->
        <property name="reasonable" value="true" />
    </plugin>
</plugins>
配置项 说明
dialect 标识是哪一种数据库,设计上必须。
offsetAsPageNum 将RowBounds第一个参数offset当成pageNum页码使用
rowBoundsWithCount 设置为true时,使用RowBounds分页会进行count查询
reasonable value=true时,pageNum小于1会查询第一页,如果pageNum大于pageSize会查询最后一页

注:上面的配置只针对于pagehelper4.x版本的.

3、开始使用pagehelper

  1. mapper文件中添加语句

    <resultMap type="Employees" id="EmployeesMapping2">
        <!-- 如果想要设置表中的id字段就必须使用 id标签 -->
        <!-- colomn属性:对应查询结果中的字段名 -->
        <!-- property:对应pojo类中的属性名 -->
        <id column="employeeNumber" property="id" />
        <result column="lastName" property="lastName" />
        <result column="firstName" property="firstName" />
        <result column="reportsTo" property="reportsTo" />
    </resultMap>
    
    <select id="queryEmployees" resultMap="EmployeesMapping2">
        SELECT employeeNumber,lastName,firstName,reportsTo FROM `employees` 
    </select>
    
  2. 在Dao接口中添加相应的方法

    List<Employees> queryEmployees();
    
  3. 开始使用

    public static void teseForEmployees() throws IOException {
         
        InputStream resourceAsStream = Resources.getResourceAsStream("SqlMapConfig.xml");
        SqlSession openSession = new SqlSessionFactoryBuilder().build(resourceAsStream).openSession();
        EmployeesDao mapper = openSession.getMapper(EmployeesDao.class);
    
        // 1、获取需要的页数和每页数据的条书
        // 正常应该是从页面获取到的数据的,这里就直接给出方便测试
        int pageNum = 1;
        int pageSize = 10;
    
        // 2、开启分页
        PageHelper.startPage(pageNum, pageSize);
    
        // 3、查询数据
        List<Employees> queryEmployees = mapper.queryEmployees();
    
        // 4、将查询到的数据传给PageInfo
        PageInfo pageInfo = new PageInfo(queryEmployees);
    
        // 5、可以开始使用了
        System.out.println(pageInfo);	// 打印pageInfo,里面封装了很多分页的信息
        List list = pageInfo.getList();
        for (Object object : list) {
         
            System.out.println(object);
        }
    }
    

4、PageInfo类里面的属性

属性 说明
pageNum 当前页
pageSize 每页的数量
size 当前页的数量
orderBy 排序
startRow 当前页面第一个元素在数据库中的行号
endRow 当前页面最后一个元素在数据库中的行号
total 总记录数
pages 总页数
list 结果集
prePage 前一页
nextPage 下一页
isFirstPage 是否为第一页
isLastPage 是否为最后一页
hasPreviousPage 是否有前一页
hasNextPage 是否有下一页
navigatePages 导航页码数
navigatepageNums 所有导航页号
navigateFirstPage 导航第一页
navigateLastPage 导航最后一页
firstPage 第一页
lastPage 最后一页