MyBatis源码分析【一】基础回顾

187 阅读7分钟

第1节 MyBatis基础使用

一、基本信息

  1. Mybatis 是 SQL映射框架
  2. 可以将数据库表中的一行数据, 映射为一个Java对象, 操作这个对象, 相当于操作表中的数据
  3. 开发人员只需要提供SQL, 通过 Mybatis 处理sql, 最终就能得到List集合或者是Java对象
  4. Mybatis是半自动ORM映射工具, 因为他在查询关联对象或关联集合对象时, 需要手动编写SQL 来实现, 所以称之为 半自动ORM映射工具

二、传统JDBC的缺陷

  1. 数据库连接创建, 释放频繁造成系统资源浪费从而会影响系统的性能, 如果说使用数据库连接池会解决这个问题

解决:在mybatis-config.xml 配置文件中配置数据库连接池,使用连接池来进行管理

  1. SQL 在 代码中是硬编码, 造成代码不易维护, 实际应用SQL的变化的可能比较大,SQL的变动需要改动Java代码

解决:将SQL语句配置在XXXmapper.xml 文件中, 与Java代码分隔

  1. 使用PreparedStatement向占位符传参存在硬编码, 因为SQL语句的where条件不一定, 可能多也可能少, 修改SQL语句还有修改代码, 系统不易维护

解决:Mybatis自动将Java对象映射到SQL语句中

  1. 对结果集解析存在硬编码

解决:Mybatis 自动将SQL执行结果映射至Java对象

三、Mybatis的优点和缺点

3.1 优点

  1. 基于SQL语句编程, 相当的灵活,不会对应用程序或者数据库的现有设计造成任何影响
  2. SQL语句编写在XML文件中, 解除了SQL与程序代码的耦合
  3. 与JDBC相比, 代码量少, 消除了JDBC中大量的冗余代码, 不需要手动开关连接
  4. 能和多种数据库兼容

3.2 缺点

  1. SQL语句的编写工作量大, 尤其当字段多,关联表比较多的是,SQL语句编写难度大
  2. SQL语句依赖于数据库, 导致数据库移植能力差

四、Mybatis的开发方式

对于MyBatis的开发方式,主要有两种方式:

  • 方式一:使用原生接口的方式
  • 方式二:使用Mapper代理的方式

现在最为常用的方式就是使用Mapper代理的方式,对于这两种方式之间的关系,将会在源码分析阶段进行分析

4.1 使用原生接口

01 准备

这里首先添加依赖

 <dependency>
   <groupId>org.mybatis</groupId>
   <artifactId>mybatis</artifactId>
   <version>3.5.1</version>
 </dependency>
 <dependency>
   <groupId>mysql</groupId>
   <artifactId>mysql-connector-java</artifactId>
   <version>8.0.13</version>
 </dependency>

步骤一:创建库表

 create table user
 (
     id      int auto_increment primary key,
     name    varchar(10)       null,
 );

步骤二:创建实体类

 @Data
 @AllArgsConstructor
 @NoArgsConstructor
 public class User {
     private Integer id;
     private String name;
 }

步骤三:创建数据操作接口

 public interface UserDao {
     /**
      * 查询出所有的用户
      * */
     public List<User> getAllUser();
     /**
      * 添加用户
      * */
     public int insertUser(User user);
     /**
      * 更新用户
      * */
     public int updateUserById(User user);
     /**
      * 删除用户
      * */
     public int deleteUserById(Integer id);
 }

02 Mapper文件

  1. namespace:通常设置为 文件所在包+文件名的形式
  2. id:这个名称要在这个这个配置文件之中是唯一的,用来标识这个标签体,一般就是写成方法名
  3. select标签:表示执行查询操作
  4. insert标签:表示执行插入操作
  5. update标签:表示执行更新操作
  6. delete标签:表示执行删除操作
  7. paramterType:调用对应方法时,参数的数据类型
  8. resultType:期望从这条语句中返回结果的类全限定名或者别名
 <?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">
 <mapper namespace="com.haolong.dao.UserDao">
     
     <select id="getAllUser" resultType="com.haolong.entity.User">
         select * from user;
     </select>
     
     <insert id="insertUser" parameterType="com.haolong.entity.User">
         insert into user(name) values(#{name})
     </insert>
     
     <update id="updateUserById" parameterType="com.haolong.entity.User">
         update user set name = #{name} where id = #{id}
     </update>
     
     <delete id="deleteUserById">
         delete from user where id = ${id};
     </delete>
     
 </mapper>

03 主配置文件

在主配置文件之中,主要完成如下几件事情:

  • 数据源的设置
  • 类型别名设置
  • Mapper文件注册
  • 配置项的设置
  • 插件的注册

主要的配置就是如图所示:

 <?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>
      <!-- 配置项的设置 -->
     <settings>
         <setting name="logImpl" value="STDOUT_LOGGING"/>
     </settings>
     <!-- 别名设置-->
     <!-- 在上一步编写Mapper文件之中,我们在编写参数类型或者返回值类型都是通过全限定名的方式,通过设置别名,我们可以直接使用 User 来代替这个全限定名-->
     <typeAliases>
         <typeAlias type="com.haolong.entity.User" alias="User"/>
     </typeAliases>
     <!-- 配置Mybatis的运行环境 default是从多个数据源中选一个 -->
     <environments default="mybatis1">
         <!--一个数据源,id值唯一-->
         <environment id="mybatis1">
             <!--配置事务管理-->
             <transactionManager type="JDBC"/>
             <!--配置连接池-->
             <dataSource type="POOLED">
                 <!--连接数据库的四个要素-->
                 <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                 <property name="url" value="jdbc:mysql://localhost:3306 /mybatis_01?serverTimezone=GMT%2B8"/>
                 <property name="username" value="root"/>
                 <property name="password" value="123456"/>
             </dataSource>
         </environment>
         <!--一个数据源,id值唯一-->
         <environment id="mybatis2">
             <!--配置事务管理-->
             <transactionManager type="JDBC"/>
             <!--配置连接池-->
             <dataSource type="POOLED">
                 <!--连接数据库的四个要素-->
                 <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                 <property name="url" value="jdbc:mysql://localhost:3306/mybatis_02?serverTimezone=GMT%2B8"/>
                 <property name="username" value="root"/>
                 <property name="password" value="123456"/>
             </dataSource>
         </environment>
     </environments>
     <mappers>
         <!--这里就是写一个配置文件的路径-->
         <mapper resource="mybatis/mapper/UserMapper.xml"/>
     </mappers>
 </configuration>

这里对里面涉及到的一些配置代码进行一些说明:

1)配置项:<transactionManager type="JDBC"/>,对于这个type的值,则由两个值:

  • JDBC:表示mybatis底层调用JDBC中的Connection 对象的 commit rollback
  • MANAGED:把mybatis的事务处理委托给其他的容器,一个服务器软件,一个框架spring

2)配置项:<dataSource type="POOLED">

  • 表示数据源, Java体系中,规定使用javax.sql.DataSoutce 接口的都是数据源,数据源表示Connection对象。

  • type的类型

    • POOLED:使用连接池,mybatis会创建PooleDataSource类
    • UPOOLED:不使用连接池,在每次执行sql语句,先创建连接,执行sql,在关闭连接mybatis会创建一个UnPooledDataSource,管理Connection的管理

3)配置文件的注册

 <mappers>
     <mapper resource="mapper/department.xml"/> <!-- 直接加载mapper.xml -->
     <mapper class="com.linkedbear.mybatis.mapper.UserMapper"/> <!-- 加载Mapper接口 -->
     <package name="com.linkedbear.mybatis.mapper"/> <!-- 包扫描Mapper接口 -->
 </mappers>

4) 将配置信息进行抽取

  • 在编写主配置文件的时候,是将数据库连接信息都是写死在里面,可以通过抽取出这些配置信息
 jdbc.url=jdbc:mysql://localhost:3306/mybatis_01?serverTimezone=UTC
 jdbc.driver=com.mysql.cj.jdbc.Driver
 jdbc.username=root
 jdbc.password=123456
  • 在主配置文件中加载并引用
 <properties resource="mysql.properties"/>
  • 使用
${key}

5)对于setting配置型,在上述代码之中,仅仅是编写了一个日志功能

04 核心对象

最后的一步,就是创建测试类型进行测试

这里首先进行测试,MyBatis的前置代码都是下面这样,

 String config = "config.xml";
 // 读取配置文件
 InputStream resourceAsStream = Resources.getResourceAsStream(config);
 // 创建 SqlSession 对象
 SqlSessionFactoryBuilder factoryBuilder = new SqlSessionFactoryBuilder();
 SqlSessionFactory factory = factoryBuilder.build(resourceAsStream);
 SqlSession sqlSession = factory.openSession();

这里对这个里面涉及到的核心对象进行说明:

  1. Resources类:mybatis中的一个类,主要负责读取配置文件

  2. SqlSessionFactoryBuilder:主要是为了创建SqlSessionFactory对象

  3. SqlSessionFactroy

    • 这是一个接口,主要是为了创建SqlSession对象
    • 程序创建这个对象,创建时间比较长,使用的资源比较多,在整个项目中使用一个就好了
    • openSession方法
  4. openSession 方法

    • 无参数:获取自动提交事务的SqlSession对象

    • 有参数

      • true:获取自动提交事务的SqlSession对象
      • false:获取非自动提交事务的·SqlSession对象 SqlSession
  5. SqlSession

    • 是一个接口,定义了操作数据的方法
    • SqlSession对象不是线程安全的, 需要在方法的内部进行使用, 在执行sql语句之前, 使用openSession()获取, 执行之后需要关闭这样就会保证线程是安全的

后续的操作,都是基于这个SqlSession来完成的

05 测试

  • 对于查询方法
 String url = "com.haolong.dao.UserDao.getAllUser";
 List<User> users = sqlSession.selectList(url);
 users.forEach(System.out::println);
  • 对于更新方法
 String url = "com.haolong.dao.UserDao.updateUserById";
 User user = new User();
 user.setId(1);
 user.setName("haolong");
 int res = sqlSession.update(url,user);
 sqlSession.commit();
 System.out.println("res = " + res);
  • 对于删除方法
 String url = "com.haolong.dao.UserDao.deleteUserById";
 sqlSession.delete(url,1);
 sqlSession.commit();
  • 对于插入方法
 String url = "com.haolong.dao.UserDao.insertUser";
 User user = new User();
 user.setName("用户一");
 int res = sqlSession.insert(url, user);
 sqlSession.commit();

不过请注意,对于更新,删除,插入这个三种类型,必须要在执行完成之后,添加sqlSession.commit(),如果不添加则在用户层面没有出错,但是实际的操作并没有落库

使用原生接口开发的方式,实际运行的时候,并没有和我们所开发的UserDao接口产生任何的关系,即使没有这个接口了,程序仍然是可以运行的。

4.2 使用Mapper代理

对于Mapper代理的这种方式,主要就是最后使用上的不同,对于前面的步骤,并没有差别

 InputStream resourceAsStream = Resources.getResourceAsStream("config.xml");
 SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(resourceAsStream);
 SqlSession sqlSession = factory.openSession();
 ​
 UserDao mapper = (UserDao) sqlSession.getMapper(UserDao.class);
 // 通过这个Mapper对象,就能够调用相应的方式
 ​

4.3 两种方式对比

  1. 使用Mapper代理这种方式相比而言更加好,主要在于可读性更加好,概念更加清晰。
  2. 并且Mapper代理方式,实际上就是使用代理设计模式对原生接口进行封装,对于这一部分的原理,将会在源码阶段进行详解
 // 如果说使用这种方式,表示一个name含义的时候,我们并不清楚,这个name表示的是人名还是狗名
 String name = "peiki";
 ​
 // 通过这种方式,我们就很容易能够知道,这个name就是一个人名
 public class User {
     private String name = "peiki";
 }

到这里为止,单表的CRUD,我们已经能够完成了,下面将会对Mapper文件之中的信息进行解释

五、SQL映射文件

5.1 statement标签

select、update、delete、insert分别对应查询,修改,删除,添加操作

 <?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">
 <mapper namespace="com.haolong.dao.UserDao">
     <select id="getAllUser" resultType="com.haolong.entity.User">
         select * from user;
     </select>
     <insert id="insertUser" parameterType="com.haolong.entity.User">
         insert into user(name) values(#{name})
     </insert>
     <update id="updateUserById" parameterType="com.haolong.entity.User">
         update user set name = #{name} where id = #{id}
     </update>
     <delete id="deleteUserById">
         delete from user where id = ${id};
     </delete>
 </mapper>

5.2 parameterType

在上一小节,编写简单的CRUD的时候,就已经说明了,这里是指参数类型:

  • 对于原生接口开发之中,就是我们调用的时候的类型,如int res = sqlSession.update(url,user)中的第二个参数
  • 对于Mapper代理的开发之中,就是方法的参数类型

对于方法的参数,无非就是基本类型,对象,多个参数等等

 # 对于基本类型
 - parameterType="java.lang.Integer"
 ​
 # 对于String类型
 - parameterType="java.lang.String"
 ​
 # 对于对象,这个全限定名的方式很长,我们可以通过在主配置文件之中,添加别名的方法,进行简写
 -  parameterType="com.haolong.entity.User"

通过这种方式,我们已经解决了传参数的问题,那么具体在Sql之中,我们如何在指定位置拼接上想要的值呢?

对于传入对象的时候

 #{对象的属性名} /  ${对象的属性名}

对于单个参数

 #{任意值}  / ${任意值}

对于多个参数(不过请注意,原生接口的方式,不支持多参数),主要是以下三种方式

  • 方式一
 # 方法代码
 public User selectUserByAllInfo(Integer id,String name);
 ​
 # 配置文件
 <select id="selectUserByAllInfo" resultType="com.haolong.entity.User">
     select * from user where id = #{param1} and name = #{param2};
 </select>
 ​
 - 这里可以使用`param1` 也可以是用`age1`。  这里的数字表示位于方法参数的第几个
 #{param1} #{param2} .... / ${param1} ${param2}.....
  • 方式二:通过使用注解
 # 方法代码
 public User selectUserByAllInfo(@Param("id") Integer id,@Param("name") String name);
 ​
 # 配置文件
 <select id="selectUserByAllInfo" resultType="com.haolong.entity.User">
     select * from user where id = #{id} and name = #{name};
 </select>
 ​
 - 通过加入@Param注解,括号中写的就是这个参数的标识符
  • 方式三:通过Map的方式
 # 方法代码
 public User selectUserByAllInfo(Map map);
 ​
 # 配置文件,#{键名}
 <select id="selectUserByAllInfo" parameterType="map" resultType="com.haolong.entity.User">
      select * from user where id = #{id} and name = #{name};
 </select>
 ​
 # 测试代码
 UserDao mapper = sqlSession.getMapper(UserDao.class);
 Map<String,String> map = new HashMap<>();
 map.put("id","3");
 map.put("name","用户三");
 User user = mapper.selectUserByAllInfo(map);

这里也补充一个小的知识点,就是使用过程之中的#{} 和 ${} 方式的对比:

  1. #{}

    • 相对安全
    • 使用的是PreparedStatement对象,可以进行预编译
    • 效率高
  2. ${}

    • 容易Sql注入
    • 使用的是Statement对象
    • 效率低

5.3 resultType

返回值类型

01 JavaBean

在上述的开发之中,使用的都是对象作为返回值类型,并且字段名称是个数据库之中是一致的,规则就是同名的列名赋值给同名的属性

那么,如果说这个数据库中的字段和实体类里面的字段名称不一样呢?

这里数据库之中是name,实体类之中是username

  • 方式一:通过修改Sql,添加别名
 select id,name as username from user;
  • 方式二:通过使用resultMap,在Mapper文件之中做修改,resultMap之中的column就表示数据库之中的列,而property就表示实体类字段
 <resultMap id="userMap" type="UserTem">
     <result column="id" property="id"/>
     <result column="name" property="username"/>
 </resultMap>
 <select id="getAllUser" resultMap="userMap">
     select * from user;
 </select>

到这里位置,我们所写的都是单表的操作,如果说对于多表操作呢

使用<resultMap>中的<association><collection>子标签,进行关联查询,这里对这两种方式进行演示

02 多表查询

一个部门有多个职工, 一个职工只能位于一个部门

创建实体类

 @Data
 @NoArgsConstructor
 @AllArgsConstructor
 public class Department {
     private Integer id;
     private String name;
     private List<Stuff> stuffList;
 }
 @Data
 @NoArgsConstructor
 @AllArgsConstructor
 public class Stuff {
     private Integer id;
     private String name;
     private Department department;
 }

创建接口方法

 public Stuff selectStuffById(Integer id);
 ​
 public Department selectDepartmentById(Integer id);

创建SQL映射文件

  • 方法一:StuffDepartment 是一对一的
 <resultMap id="stuffMap" type="Stuff">
     <id property="id" column="id"></id>
     <result property="name" column="name"></result>
     <association property="department" javaType="com.haolong.entity.Department">
         <id property="id" column="dId"></id>
         <result property="name" column="dName"></result>
     </association>
 </resultMap>
 <select id="selectStuffById" parameterType="integer" resultMap="stuffMap">
     select stuff.id as id,stuff.name as name,D.id as dId,D.name as dName
     from stuff
     left join Department D on stuff.d_id = D.id
     where stuff.id = #{id};
 </select>
  • 方法二:DepartmentStuff 是 一对多的
 <resultMap id="departmentMap" type="Department">
     <id property="id" column="id"/>
     <result property="name" column="name"></result>
     <collection property="stuffList" ofType="Stuff">
         <id column="sId" property="id"></id>
         <result column="sName" property="name"></result>
     </collection>
 </resultMap>
 ​
 <select id="selectDepartmentById" parameterType="integer" resultMap="departmentMap" resultType="Department">
     select d.id as id, d.name as name,s.id as sId,s.name as sName
     from department d
     left join stuff s on d.id = s.d_id
     where d.id = #{id};
 </select>

在上面的方式之中,在查询的过程之中,我们查询员工信息或者查询部门信息的时候,会查询出所有信息,比如说现在查询的时候,如果说我们想查询员工信息的时候,在这个时候,则是同样会将部门信息查询出来,但是如果说我们只想要员工信息,不想要部门信息的时候呢?只能够重新编写一个方法,这种多表处理的方式也叫做主动加载

但是其实还有一种方式,就是延迟加载,这也对下面这种方式进行讲解

延迟加载

  • 延迟加载也叫懒加载,惰性加载,使用延迟加载可以提高程序的运行效率,针对于数据持久层的操作,在某些特定的情况之下访问特定的数据库,在其他情况之下可以不访问某些表,从一定程度上减少了Java应用于数据库的交互次数
  • 查询学生和班级的时候,学生和班级是两张不同的表,如果当前需求只需要获取学生的信息,那么查询学生单标即可,如果需要通过学生获取对应的班级信息,那么必须要查询两张表
  • 不同的业务需求,需要查询不同的表根据具体的业务需求来动态的减少数据表查询的工作就是延迟加载

这个时候,同样写代码进行验证

  • 在Mybatis的主配置文件中,开启延迟加载
 <settings>
   <setting name="logImpl" value="STDOUT_LOGGING"/>
   <setting name="lazyLoadingEnabled" value="true"/>
 </settings>
  • 将多表关联查询拆分成多个单表查询
 <?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">
 <mapper namespace="com.haolong.dao.UserDao">
 ​
     <resultMap id="stuffMap" type="Stuff">
         <id property="id" column="id"></id>
         <result property="name" column="name"></result>
         <association
                      property="department"
                      javaType="Department"
                      select="com.haolong.dao.UserDao.selectDepartmentById"
                      column="d_id"
                      />
     </resultMap>
 ​
     <resultMap id="departmentMap" type="Department">
         <id property="id" column="id"/>
         <result property="name" column="name"></result>
         <collection
                     select="com.haolong.dao.UserDao.selectStuffByDepartId"
                     column="id"
                     property="stuffList"
                     ofType="Stuff"
                     />
     </resultMap>
 ​
     <select id="selectStuffById" parameterType="integer" resultType="Stuff" resultMap="stuffMap">
         select * from stuff where id = #{id}
     </select>
     <select id="selectDepartmentById" parameterType="integer" resultMap="departmentMap">
         select * from department where id = #{id}
     </select>
     <select id="selectStuffByDepartId" parameterType="integer" resultType="Stuff">
         select * from stuff where d_id = #{id}
     </select>
 </mapper>

六. 工具类

在测试文件中,对于SqlSession的获取,这段代码都是重复的,所以,我们可以抽取工具类

 public class MybatisUtils {
     private MybatisUtils(){}
     private static SqlSessionFactory sqlSessionFactory;
     static{
         String config = "mybatis-config.xml";
         try {
             InputStream in = Resources.getResourceAsStream(config);
             sqlSessionFactory = new SqlSessionFactoryBuilder().build(in);
         } catch (IOException e) {
             e.printStackTrace();
         }
 ​
     }
     public static SqlSession getSession(){
         SqlSession sqlSession = null;
         if(sqlSessionFactory != null) {
             sqlSession = sqlSessionFactory.openSession();
         }
         return sqlSession;
     }
 }

七、动态sql

sql语句的内容是变化的,可以根据条件获取到不同的是sql

7.1 select

01 if

if 标签可以根据表达式的值,来决定是否将对应的语句添加到 SQL 中

比如说,我现在有一个实体类User,里面有两个属性 Id 和 name,如果想通过ID查询一个用户,则对应的Mapper文件之中的标签可以和下面一样

 public User selectUser(User user);
 <select id="selectUser" parameterType="User" resultType="User">
     select id,name from user where id = #{id};
 </select>

但是,如果说我现在想 Id 和 name 做一个全值匹配,就只能重新写一个Mapper标签

 <select id="selectUser" parameterType="User" resultType="User">
     select id,name from user where id = #{id} and name = #{name};
 </select>

如果说此时,我想将两个Mapper标签整合为一个,也就是说最初就是通过ID来查用户,如果说我传入的参数之中,name属性不为空,我在通过Id 查用户的时候,在对应的Sql之后拼接,and name = #{name}即可,在MyBatis之中,提供了这种方式

但是MyBatis之中,提供了一种特殊的,语法如下:

 <if test="判断java对象的属性">
     部分sql语句
 </if>

所以原来的两个Mapper可以整合为如下的这种方式

 <select id="selectUser" parameterType="User" resultType="User">
     select * from user where id = #{id}
     <if test="name != null ">
         and name = #{name}
     </if>
 </select>

02 where

用于包含多个的,当有多个if 有一个成立的时候,会自动增加一个where关键字,并且会去掉 if 中多余的and 和 or。通常情况下 if 和 where 结合起来使用

这里我们定义一个查询所有用户的方法

 public List<User> selectUser();
 <select id="selectUser"  resultType="User">
     select * from user
 </select>

如果说我们现在原来的Sql后面根据参数动态的进行拼接,则可以使用上面所说的if标签

public List<User> selectUser(@Param("id") Integer id);
<select id="selectUser" parameterType="integer" resultType="User">
    select * from user where 1 = 1
    <if test="id != null">
        id > #{id}
    </if>
</select>

注意到,这里在where条件之后,添加了1 = 1,之所以这样做,是因为如果说id为空了,where之后就没有东西了,就会出错,虽然说这种方式可行,但是MyBatis为我们提供了<where>标签,也就是下面这种方式

<select id="selectUser" parameterType="integer" resultType="User">
    select * from user
    <where>
        <if test="id != null">
            id > #{id}
        </if>
    </where>
</select>

这个 <where> 标签就是一个光秃秃的标签,没有任何属性,就是为了帮我们构造 where 的,而且也帮我们除去了第一个不必要的 and ,比如说下面这种情况

 <select id="selectUser" resultType="User">
     select * from user
     <where>
         <if test="id != null">
             and id = #{id}
         </if>
         <if test="name != ''">
             and name = #{name}
         </if>
     </where>
 </select>

Where搭配if使用的时候,都会在里面的语句前面添加一个and,但是如果说这个and添加在之后,就会在原来的语句后面多出一个and,导致程序出错,就比如下面这种情况

 <select id="selectUser" resultType="User">
     select * from user
     <where>
         <if test="id != null">
             id = #{id} and
         </if>
         <if test="name != ''">
             name = #{name} and
         </if>
     </where>
 </select>

为了处理这种特殊的写法,MyBatis中提供了trim标签

03 trim

在这个标签之中,提供了四个属性

  • prefix :在整个标签前面附加指定内容
  • prefixOverrides :去掉标签体内第一个指定的关键字
  • suffix :在整个标签最后追加指定内容
  • suffixOverrides :去掉标签体内最后一个指定的关键字
 <select id="selectUser" resultType="User">
     select * from user
     <trim prefix="where" prefixOverrides="and" suffixOverrides="and" suffix="">
         <if test="id != null">
             and id = #{id} and
         </if>
         <if test="name != ''">
             name = #{name} and
         </if>
     </trim>
 </select>

原来的Sql就变为了下面这种情况:

select * from user where id = ? and name = ? 

04 choose

使用 <where> 配合 <if> 标签,已经可以满足日常开发的绝大多数场景的需求了,不过还有一种情况是用 <if> 很难解决的:如果一条查询中有多个条件,每次只让其中一个条件生效(类似于 if - elseif - else ),这样用 <if> 确实很难。MyBatis 同样考虑到了这种情况,于是给了我们另外 3 个标签来解决,也就是:<choose>whenotherwise

 <select id="selectUser" resultType="User">
     select * from user
     <choose>
         <when test="id != null">
             where id = #{id}
         </when>
         <when test="name != null">
             where name = #{name}
         </when>
         <otherwise>
             where id > 3;
         </otherwise>
     </choose>
 </select>

05 foreach

可以迭代生成一系列的值,这个标签主要用于SQL 中的 in 语句

 public List<User> selectUser(List<Integer> list);
 <select id="selectUser" resultType="User">
     select * from user
     where id in
     <foreach collection="list" item="item" open="(" close=")" separator=",">
         #{item}
     </foreach>
 </select>

7.2 update

01 set

用于update 操作,会自动根据参数选择生成SQL语句

 <update id="updateUserById" parameterType="com.haolong.entity.User">
     update user
     <set>
         <if test="name != null">
             name = #{name}
         </if>
     </set>
     where id = #{id}
 </update>

不过这个标签也是可以通过trim标签进行替换

<update id="updateUserById" parameterType="com.haolong.entity.User">
    update user
    <trim prefix="set">
        <if test="name != null">
            name = #{name}
        </if>
    </trim>
    where id = #{id}
</update>

02 foreach

在 update 中可以巧妙利用模型类转 Map 这样的思路,配合 <foreach> 标签,可以将 update 简化。

1)导入依赖

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.1</version>
</dependency>

2) 实体类转为Map,不过也可以直接传入Map,不用实体类这种方式

 BeanMap beanMap = BeanMap.create(user);

3)接口

 public int updateUser(@Param("id") Integer id,@Param("map") Map map);

foreach 在循环 Map 时,键值对的 key 是 index ,value 是 item

 <update id="updateUser">
     update user
     <foreach collection="map" index="key" item="value" open="set" separator=",">
         ${key} = #{value}
     </foreach>
     where id = #{id}
 </update>

7.3 SQL 复用

 <sql id="名称">
    抽取出来的代码片段
 </sql>

在我们具体使用的时候,则可以通过下面这种方式使用

 <include refid="抽取出来的SQL名称"></include>

具体的案例如下:

 <sql id="selectSql">
     select id,name from user
 </sql>
 <select id="getAllUser" resultType="com.haolong.entity.User">
     <include refid="selectSql"></include>
 </select>
 <select id="selectUser" resultType="User">
     <include refid="selectSql"></include>
     where id in
     <foreach collection="list" item="item" open="(" close=")" separator=",">
         #{item}
     </foreach>
 </select>

注意,sql标签,同样支持传递参数,不过在sql标签里面使用时候,必须加${属性名}

 <sql id="selectSql">
     select id
     <if test="${name} != null and ${name} == ture">
         ,name
     </if>
     from user
 </sql>
 <select id="getAllUser" resultType="com.haolong.entity.User">
     <include refid="selectSql">
         <property name="name" value="true"/>
     </include>
 </select>

八、分页

这里也将对MyBatis过程之中,常用的分页演示

  • 方式一:通过Sql之中的Limit
 select * from department where id = #{id} limit #{startIndex},#{pageSize}
  • 方式二:通过RowBounds
 String url = "com.haolong.dao.UserDao.getAllUser";
 RowBounds rowBounds = new RowBounds(1,3);
 ​
 List<User> users = sqlSession.selectList(url,null,rowBounds);
  • 方式三:通过开发拦截器(这种方式,在手续会有演示)
  • 方式四:通过使用分页插件

这个时候也对分页插件的使用进行演示

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

在mybatis主配置文件中进行插件注册

     <plugins>
         <plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin>
     </plugins>

创建测试类

PageHelper.startPage(起始页数,一页有多少条数据);

 @Test
 public void test8(){
 ​
     SqlSession sqlSession  = MybatisUtils.getSqlSession();
 ​
     PageHelper.startPage(1,3);
 ​
     List<Student> students = sqlSession.getMapper(StudentImpl.class).getAllStudent();
 ​
     System.out.println(students.size());
 ​
     for(Student s:students){
 ​
         System.out.println(s);
 ​
     }
 ​
 }