一、什么是缓存?
缓存,合理使用缓存是优化中最常见的,将从数据库中查询出来的数据放入缓存中,下次使用时不必从数据库查询,而是直接从缓存中读取,避免频繁操作数据库,减轻数据库的压力,同时提高系统性能。
一级缓存:是 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 工作机制
- 一个会话, 查询一条数据, 这个数据就会被保存在当前会话的一次缓存(SqlSession)中
- 如果关闭会话, 那么一次缓存中的数据就会被保存到二级缓存(namespace)中,新的会话查询的内容, 就可以参照二级缓存
- 一个 xxxMapper 对应一个 namespace, 不同的 namespace 查出的数据会保存在自己对应的二级缓存中
-
查出的数据会先被保存在一级缓存中, 只有会话关闭或者提交之后, 以及缓存中的数据才会被转移到二级缓存
2.2 开启二级缓存步骤
- 开启全局二级缓存配置
- 去 mapper.xml 中配置使用二级缓存
- 我们的 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();
}
}
结果如下:
2.4 其他配置
①. cacheEnabled 设置的是二级缓存,一级缓存也一直可以使用
②. 每个 select 标签都有 useCache
标签,如果设置 useCache="false"
,那么一级缓存依旧使用,二级缓存则不会使用
③. 每个增删改查操作的 flushCache="true"
, 表示增删改查操作完成之后, 会自动清除缓存, 此时一级缓存和二级缓存都会被清空