MyBatis学习
- 官方文档:mybatis – MyBatis 3 | 简介
- Github:mybatis/mybatis-3: MyBatis SQL mapper framework for Java (github.com)
- 参考视频:【狂神说Java】Mybatis最新完整教程IDEA版通俗易懂_哔哩哔哩_bilibili
- 项目完整代码参考:lexiaoyuan/MyBatisStudy: My MyBatis study notes (github.com)、MyBatisStudy: 我的MyBatis学习笔记 (gitee.com)
缓存
MyBatis 内置了一个强大的事务性查询缓存机制,它可以非常方便地配置和定制。 为了使它更加强大而且易于配置,我们对 MyBatis 3 中的缓存实现进行了许多改进。
默认情况下,只启用了本地的会话缓存(
getSqlSession()
到sqlSession.close()
),它仅仅对一个会话中的数据进行缓存。
- 默认情况下,一级缓存开启。(SqlSession级别的缓存,也称为本地缓存)
- 二级缓存需要手动开启,是基于Mapper.xml中的namespace级别的缓存
一级缓存
- 与数据库同一次会话期间查询到的数据会放在本地缓存中
- 以后如果需要获取相同的数据,直接从缓存中拿,不会重新执行查询语句
测试:
- 搭环境(开启日志)
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private int id;
private String name;
private String pwd;
}
public interface UserMapper {
User queryUserById(@Param("id") int id);
int updateUser(User user);
}
<!-- UserMapper.xml -->
<select id="queryUserById" resultType="user">
select * from user where id=#{id}
</select>
<update id="updateUser" parameterType="user">
update user set name=#{name},pwd=#{pwd} where id=#{id}
</update>
<!-- mybatis-config.xml -->
<mappers>
<mapper resource="com/mybatis/dao/UserMapper.xml"/>
</mappers>
- 测试
- 测试1:在一个会话中,连续查询同一个用户
@Test
public void getUserByIdTest() {
SqlSession sqlSession = MyBatisUtil.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = mapper.queryUserById(1);
System.out.println(user);
System.out.println("===============");
User user2 = mapper.queryUserById(1);
System.out.println(user2);
sqlSession.close();
}
结果:第二次查询没有重新执行select语句,而是通过缓存读取
- 测试2:在一个会话中,查询不同的用户
@Test
public void getUserByIdTest() {
SqlSession sqlSession = MyBatisUtil.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = mapper.queryUserById(1);
System.out.println(user);
System.out.println("===============");
User user2 = mapper.queryUserById(3);
System.out.println(user2);
sqlSession.close();
}
结果:查询不同的用户,会重新执行select语句
- 测试3:更新另一个用户,查询同一个用户
@Test
public void getUserByIdTest() {
SqlSession sqlSession = MyBatisUtil.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = mapper.queryUserById(1);
System.out.println(user);
mapper.updateUser(new User(3, "update","updateUser"));
System.out.println("===============");
User user2 = mapper.queryUserById(1);
System.out.println(user2);
System.out.println(user==user2);
sqlSession.close();
}
结果:更新另一个用户,查询同一个用户,仍然会重新执行select语句。所有的增删改都会刷新缓存。
- 测试4:查询同一个用户,手动清理缓存
@Test
public void getUserByIdTest() {
SqlSession sqlSession = MyBatisUtil.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = mapper.queryUserById(1);
System.out.println(user);
//mapper.updateUser(new User(3, "update","updateUser"));
sqlSession.clearCache(); //手动清理缓存
System.out.println("===============");
User user2 = mapper.queryUserById(1);
System.out.println(user2);
System.out.println(user==user2);
sqlSession.close();
}
结果:会重新执行select语句
二级缓存
- 也称全局缓存
- 基于namespace级别的缓存,一个命名空间对应一个二级缓存
- 一级缓存中的数据会保存到为二级缓存中,当一个会话关闭时,一级缓存中的数据没了,可以从二级缓存中获取
- 不同mapper查出的数据会放在自己对应的缓存(相当于一个map)中
测试:
- 开启全局缓存(核心配置文件中设置)
<!-- 显示的开启全局缓存-->
<setting name="cacheEnabled" value="true"/>
- 在要使用的Mapper.xml中开启
- 简单开启:
<!-- 在当前Mapper.xml中开启二级缓存 -->
<cache />
- 自定义:
<!-- 在当前Mapper.xml中开启二级缓存 -->
<!--
创建了一个 FIFO 缓存,每隔 60 秒刷新,最多可以存储结果对象或列表的 512 个引用,
而且返回的对象被认为是只读的
-->
<cache
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>
- 测试
- 测试1:开启两个会话,查询同一个用户
@Test
public void getUserByIdTest2() {
SqlSession sqlSession = MyBatisUtil.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
SqlSession sqlSession2 = MyBatisUtil.getSqlSession();
UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);
User user = mapper.queryUserById(1);
System.out.println(user);
System.out.println("===============");
User user2 = mapper2.queryUserById(1);
System.out.println(user2);
System.out.println(user==user2);
sqlSession.close();
sqlSession2.close();
}
结果:第二次查询会重新连接数据库和重新执行select语句
- 测试2:第一个会话关闭后,在第二个会话中查询同一个用户
@Test
public void getUserByIdTest2() {
SqlSession sqlSession = MyBatisUtil.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
SqlSession sqlSession2 = MyBatisUtil.getSqlSession();
UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);
User user = mapper.queryUserById(1);
System.out.println(user);
sqlSession.close();
System.out.println("===============");
User user2 = mapper2.queryUserById(1);
System.out.println(user2);
System.out.println(user==user2);
sqlSession2.close();
}
结果:第二次查询从缓存中获取,没有重新连接数据库和执行select语句。
遇到的问题:
- 使用简单的缓存
<cache />
,需要将实体类序列化,否则会报错:
Caused by: java.io.NotSerializableException: com.mybatis.pojo.User
将实体类序列化:
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User implements Serializable {
private int id;
private String name;
private String pwd;
}
小结:
- 在同一个Mapper下才有效
- 所有的数据会先放到一级缓存下
- 只有当会话提交或关闭时,才会提交到二级缓存中
缓存原理
- 缓存顺序:
- 一次查询,先走二级缓存,如果二级缓存中没有,再看一级缓存中有没有,如果一级缓存中没有就连接数据库查询。
自定义缓存-EhCache
Ehcache是一种广泛使用的开源Java分布式缓存。主要面向通用缓存,Java EE和轻量级容器。