MyBatis | 一级缓存与二级缓存
一、什么是缓存?
缓存,合理使用缓存是优化中最常见的,将从数据库中查询出来的数据放入缓存中,下次使用时不必从数据库查询,而是直接从缓存中读取,避免频繁操作数据库,减轻数据库的压力,同时提高系统性能。
一级缓存:是 SQlSession 级别的缓存。在操作数据库时需要构造 SqlSession 对象,在对象中有一个数据结构(HashMap)用于存储缓存数据。不同的 SqlSession 之间的缓存数据区域(HashMap)是互相不影响的。
二级缓存:是 mapper 级别的缓存,多个 SqlSession 去操作同一个mapper的sql语句,多个 SqlSession 可以共用二级缓存,二级缓存是跨 SqlSession 的。
二、具体介绍
1.使用一级缓存
我们先看一个使用缓存例子:这里我们是根据传入的 id 获取 Employee 对象的值,我们先使用同一个 SqlSession 对象,并且查询 id 相同的对象
@Test
public void testFirstCache() throws IOException {
SqlSessionFactory sqlSessionFactory = Utils.getSqlSessionFactoty();
SqlSession openSession = sqlSessionFactory.openSession();
try {
EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
Employee employee = mapper.getEmployee(1);
System.out.println(employee);
Employee employee2 = mapper.getEmployee(1);
System.out.println(employee2);
System.out.println(employee==employee2);
} finally {
openSession.close();
}
}
他的运行结果是:
从结果中可以看出,第一次查询结束之后,就把 id 为 1 的数据放入到缓存中(本质上是放入 Map 对象中),第二次如果还是使用相同的 SqlSession 对象,则先会去一级缓存中找是否有 id 为 1 的员工的信息(Map 对象中是否有该对象),如果有,则直接从缓存中取出员工信息,如果没有,则会去数据库中查询相关信息
我们再来看一个一级缓存失效了的例子:假设这个时候我们查询的不是同一个 id,我们看看结果是怎样。在上面的代码中,我们将 Employee employee2 = mapper.getEmployee(1)
改为 Employee employee2 = mapper.getEmployee(2)
,即不是查询同一个 id,结果如下: 这也验证了我上面的结论
一级缓存失效的情况
- SqlSession 对象不同, 导致每次使用的都是新的 SqlSession 对象
- SqlSession 相同, 查询条件不同, 此时一级缓存中没有数据, 因为两次查询的内容不一样
- SqlSession 相同, 两次查询直接执行了增删改查操作, 因为这次操作可能会对当前数据库有影响
- SqlSession 相同, 手动清除了一级缓存(
openSession.clearCache()
)
这些情况就不一一举例了,以后用到关注一下即可
2. 使用二级缓存
EmployeeMapper 有一个二级缓存区域(按 namespace 分),其它 Mapper 也有自己的二级缓存区域(按 namespace 分)。每一个 namespace 的 mapper 都有一个二级缓存区域,两个 mapper 的 namespace 如果相同,这两个 mapper 执行 sql 查询到数据将存在相同的二级缓存区域中。
2.1 工作机制
1. 一个会话, 查询一条数据, 这个数据就会被保存在当前会话的一次缓存(SqlSession)中
2. 如果关闭会话, 那么一次缓存中的数据就会被保存到二级缓存(namespace)中,新的会话查询的内容, 就可以参照二级缓存
3. 一个 xxxMapper 对应一个 namespace, 不同的 namespace 查出的数据会保存在自己对应的二级缓存中
4. 查出的数据会先被保存在一级缓存中, 只有会话关闭或者提交之后, 以及缓存中的数据才会被转移到二级缓存
2.2 开启二级缓存步骤
1. 开启全局二级缓存配置
2. 去 mapper.xml 中配置使用二级缓存
3. 我们的 POJO 需要实现序列化接口
在全局文件中开启二级缓存
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
在 sql 映射文件中配置使用二级缓存,具体参数可以去官方文档查询
<mapper namespace="edu.just.mybatis.dao.EmployeeMapper">
<cache eviction="FIFO" flushInterval="60000" readOnly="false" size="1024"></cache>
...
</mapper>
实现序列化
public class Employee implements Serializable{
private static final long serialVersionUID = 1L;
private Integer id;
private String lastName;
private String email;
private Integer gender;
private Department department;
...
}
2.3 二级缓存测试
这里我们通过两个不同的 SqlSession 对象创建两个 EmployeeMapper,这两个 EmployeeMapper 属于同一个 namespace
@Test
public void testSecondCache() throws IOException {
SqlSessionFactory sqlSessionFactory = Utils.getSqlSessionFactoty();
SqlSession openSession = sqlSessionFactory.openSession();
SqlSession openSession2 = sqlSessionFactory.openSession();
try {
//1.创建两个不同的 EmployeeMapper 对象
EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
EmployeeMapper mapper2 = openSession2.getMapper(EmployeeMapper.class);
Employee employee = mapper.getEmployee(1);
openSession.close(); //关闭第第一个 SqlSession 会话
//2.第二次查询是从二级缓存中拿到的, 并没有发送新的 sql
// 此时是从 EmployeeMapper 的二级缓存中获取的数据
Employee employee2 = mapper2.getEmployee(1);
System.out.println(employee2);
openSession.close();
} finally {
openSession.close();
}
}
结果如下:
可以看到,虽然我们使用的是两个 SqlSession 对象,但是范围依旧是在 EmployeeMapper 这个 namespace 中,当我们关闭了第一个 SqlSession 对象后,此时第一次缓存中的数据被保存在了 EmployeeMapper 的二级缓存中,因此当我们重新查询相同的 id 的数据时,这个时候是在二级缓存中进行获取的
2.4 其他配置
①. cacheEnabled 设置的是二级缓存,一级缓存也一直可以使用
②. 每个 select 标签都有 useCache
标签,如果设置 useCache="false"
,那么一级缓存依旧使用,二级缓存则不会使用
③. 每个增删改查操作的 flushCache="true"
, 表示增删改查操作完成之后, 会自动清除缓存, 此时一级缓存和二级缓存都会被清空