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 自然也就不同。