目录

Mybatis 实践

MyBatis 工作流程

  • 读取配置文件 mybatis-config.xml
  • 加载映射文件 xxxMapper.xml
  • 构造会话工厂 SqlSessionFactory
  • 创建会话 SqlSession
  • Executor 执行器
  • MappedStatement 对象:在 Executor 接口的执行方法中有一个 MappedStatement 类型的参数,该参数是对映射信息的封装,用于存储要映射的 SQL 语句的 id、参数等信息。
  • 输入参数映射:输入参数类型可以是 Map、List 等集合类型,也可以是基本数据类型和 POJO 类型。输入参数映射过程类似于 JDBC 对 preparedStatement 对象设置参数的过程。
  • 输出结果映射:输出结果类型可以是 Map、 List 等集合类型,也可以是基本数据类型和 POJO 类型。输出结果映射过程类似于 JDBC 对结果集的解析过程。

批量插入

  • for 循环 insert
  • batch 模式
  • foreach 动态拼接sql批量插入
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class UserDaoImpl implements UserDao {
  // SqlSessionFactory 一般会由 SqlSessionDaoSupport 进行设置
  private final SqlSessionFactory sqlSessionFactory;

  public UserDaoImpl(SqlSessionFactory sqlSessionFactory) {
    this.sqlSessionFactory = sqlSessionFactory;
  }

  public void batchInsert() {
    try (SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH, false)) {
      UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
      long start = System.currentTimeMillis();
        for (int i = 0; i < 100000; i++) {
            User user = new User();
            user.setId("id" + i);
            user.setName("name" + i);
            user.setPassword("password" + i);
            userMapper.insert(user);
        }
        sqlSession.commit();
        long end = System.currentTimeMillis();
        System.out.println("---------------" + (start - end) + "---------------");
    }
  }
} 
1
2
3
4
5
6
7
8
<insert id="insertBatch">
        INSERT INTO t_user
        (id, name, password)
        VALUES
        <foreach collection ="userList" item="user" separator =",">
            (#{id}, #{name}, #{password})
        </foreach >
    </insert>

默认情况下 MySQL 可以执行的最大 SQL(大小)为 4M

1
2
-- 设置最大执行 SQL 为 10M MySQL 连接的客户端中执行
set global max_allowed_packet=10*1024*1024;

但以上解决方案仍是治标不治本,因为我们无法预测程序中最大的执行 SQL 到底有多大,那么最普世的方法就是分配执行批量插入的方法了, Mybatis Plus 中批量插入就是这样的。

传递多个参数

使用map接口传递参数

1
public List<Role> findRolesByMap(Map<String, Object> parameterMap);
1
2
3
<select id="findRolesByMap" parameterType="map" resultType="role">
    select id, role_name as roleName, note from t_role where role_name like concat('%', #{roleName}, '%') and note like concat('%', #{note}, '%')
</select>

使用注解传递多个参数

1
public List<Role> findRolesByAnnotation(@Param("roleName") String rolename, @Param("note") String note);
1
2
3
<select id="findRolesByAnnotation" resultType="role">
    select id, role_name as roleName, note from t_role where role_name like concat('%', #{roleName}, '%') and note like concat('%', #{note}, '%')
</select>

通过Java Bean传递多个参数

1
2
public List<Role> findRolesByBean(RoleParams roleParam);
public List<Role> findByMix(@Param("params") RoleParams roleParams, @Param("page") PageParam PageParam);
1
2
3
4
5
6
7
<select id="findRolesByBean" parameterType="com.xc.pojo.RoleParams" resultType="role">
    select id, role_name as roleName, note from t_role where role_name like concat('%', #{roleName}, '%') and note like concat('%', #{note}, '%')
</select>
<select id="findByMix" resultType="role">
    select id, role_name as roleName, note from t_role
    where role_name like concat('%', #{params.roleName}, '%') and note like concat('%', #{params.note}, '%') limit #{page.start}, #{page.limit}
</select>

缓存机制

一级缓存为 ​SqlSession​ 缓存,缓存的数据只在 SqlSession 内有效。在操作数据库的时候需要先创建 SqlSession 会话对象,在对象中有一个 HashMap 用于存储缓存数据,此 HashMap 是当前会话对象私有的,别的 SqlSession 会话对象无法访问。

二级缓存是​ mapper​ 级别的缓存,也就是同一个 namespace 的 mapper.xml ,当多个 SqlSession 使用同一个 Mapper 操作数据库的时候,得到的数据会缓存在同一个二级缓存区域

二级缓存默认是没有开启的。需要在 setting 全局参数中配置开启二级缓存

全局变量开启二级缓存

1
2
3
<settings>
    <setting name="cacheEnabled" value="true"/>
<settings>

当前 mapper 下所有语句开启二级缓存

1
2
3
<cache eviction="LRU" flushInterval="60000" size="512" readOnly="true"/>
<!-- 单条禁用 -->
<select id="getCountByName" parameterType="java.util.Map" resultType="INTEGER" statementType="CALLABLE" useCache="false">

延迟加载

Mybatis 仅支持 association 关联对象和 collection 关联集合对象的延迟加载,association 指的就是一对一,collection 指的就是一对多查询。在 Mybatis 配置文件中,可以配置是否启用延迟加载lazyLoadingEnabled=true|false。

延迟加载的基本原理是,使用 CGLIB 创建目标对象的代理对象,当调用目标方法时,进入拦截器方法。不光是 Mybatis,几乎所有的包括 Hibernate,支持延迟加载的原理都是一样的。

#{}和 ${}的区别是什么?

  • ${}是字符串替换,#{}是预处理;
  • Mybatis 在处理${}时直接替换成变量的值。而 Mybatis 在处理 #{}时,会对 sql 语句进行预处理,将 sql 中的 #{}替换为?号,调用 PreparedStatement 的 set 方法来赋值;
  • 使用 #{}可以有效的防止 SQL 注入,提高系统安全性。

批量插入返回主键列表

1
2
3
4
5
6
7
<insert id="insertAuthor" useGeneratedKeys="true"
    keyProperty="id">
  insert into Author (username, password, email, bio) values
  <foreach item="item" collection="list" separator=",">
    (#{item.username}, #{item.password}, #{item.email}, #{item.bio})
  </foreach>
</insert>

SelectKey在Mybatis中是为了解决Insert数据时不支持主键自动生成的问题,他可以很随意的设置生成主键的方式。

不管SelectKey有多好,尽量不要遇到这种情况吧,毕竟很麻烦。

like 模糊查询推荐

1
2
string wildcardname = "%smi%";
list<name> names = mapper.selectlike(wildcardname);
1
2
3
4
5
6
7
<select id="selectlike">
    select * from foo where bar like #{value}
</select>
<!-- 不推荐 会 sql 注入 -->
<select id=”selectlike”>
select * from foo where bar like "%"#{value}"%"
</select>

Executor 执行器

Mybatis 有三种基本的 Executor 执行器,「SimpleExecutor、ReuseExecutor、BatchExecutor。」

SimpleExecutor:每执行一次 update 或 select,就开启一个 Statement 对象,用完立刻关闭 Statement 对象。

ReuseExecutor:执行 update 或 select,以 sql 作为 key 查找 Statement 对象,存在就使用,不存在就创建,用完后,不关闭 Statement 对象,而是放置于 Map<String, Statement>内,供下一次使用。简言之,就是重复使用 Statement 对象。

BatchExecutor:执行 update(没有 select,JDBC 批处理不支持 select),将所有 sql 都添加到批处理中(addBatch()),等待统一执行(executeBatch()),它缓存了多个 Statement 对象,每个 Statement 对象都是 addBatch()完毕后,等待逐一执行 executeBatch()批处理。与 JDBC 批处理相同。

作用范围:Executor 的这些特点,都严格限制在 SqlSession 生命周期范围内。

Mapper 接口

  • xxxMapper.xml 文件中的 namespace 即是 Mapper 接口的全限定类名。
  • Mapper 接口方法名 和 xxxMapper.xml 中定义的 sql 语句 id 一一对应。
  • Mapper 接口方法的输入参数类型和 xxxMapper.xml 中定义的每个sql语句的 parameterType 的类型相同。
  • Mapper 接口方法的输出参数类型和 xxxMapper.xml 中定义的每个sql语句的 resultType 的类型相同。
1
2
3
4
5
<?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.ynthm.demo.mybatis.user.mapper.RoleMapper">

</mapper>

Mapper 接口的工作原理

Mapper 接口。接口的全限名,就是映射文件中的 namespace 的值;接口的方法名,就是映射文件中 Mapper 的 Statement 的 id 值;接口方法内的参数,就是传递给 sql 的参数。

Mapper 接口是没有实现类的,当调用接口方法时,接口全限名+方法名拼接字符串作为 key 值,可唯一定位一个 MapperStatement。

Mapper 接口里的方法,是不能重载的,因为是使用 全限名+方法名 的保存和寻找策略。Mapper 接口的工作原理是 JDK 动态代理,Mybatis 运行时会使用 JDK动态代理为 Mapper 接口生成代理对象 proxy,代理对象会拦截接口方法,转而执行 MapperStatement 所代表的 sql,然后将 sql 执行结果返回。

接口里的方法,是不能重载的,因为是全限名+方法名的保存和寻找策略,需要保证全限名+方法名的唯一性。

不同的 Xml 映射文件,如果配置了 namespace,那么 id 可以重复;如果没有配置 namespace,那么 id 不能重复;毕竟 namespace 不是必须的,只是最佳实践而已。

原因就是 namespace+id 是作为 Map<String, MappedStatement> 的 key 使用的,如果没有 namespace,就剩下 id,那么,id 重复会导致数据互相覆盖。有了 namespace,自然 id 就可以重复,namespace 不同,namespace+id 自然也就不同。