MyBatis | 一级缓存与二级缓存

MyBatis | 一级缓存与二级缓存_第1张图片

一、什么是缓存?

缓存,合理使用缓存是优化中最常见的,将从数据库中查询出来的数据放入缓存中,下次使用时不必从数据库查询,而是直接从缓存中读取,避免频繁操作数据库,减轻数据库的压力,同时提高系统性能。

MyBatis | 一级缓存与二级缓存_第2张图片

一级缓存:是 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();
    }
    
}

他的运行结果是:
MyBatis | 一级缓存与二级缓存_第3张图片

从结果中可以看出,第一次查询结束之后,就把 id 为 1 的数据放入到缓存中(本质上是放入 Map 对象中),第二次如果还是使用相同的 SqlSession 对象,则先会去一级缓存中找是否有 id 为 1 的员工的信息(Map 对象中是否有该对象),如果有,则直接从缓存中取出员工信息,如果没有,则会去数据库中查询相关信息

我们再来看一个一级缓存失效了的例子:假设这个时候我们查询的不是同一个 id,我们看看结果是怎样。在上面的代码中,我们将 Employee employee2 = mapper.getEmployee(1) 改为 Employee employee2 = mapper.getEmployee(2),即不是查询同一个 id,结果如下:

MyBatis | 一级缓存与二级缓存_第4张图片
这也验证了我上面的结论

一级缓存失效的情况
  • SqlSession 对象不同, 导致每次使用的都是新的 SqlSession 对象
  • SqlSession 相同, 查询条件不同, 此时一级缓存中没有数据, 因为两次查询的内容不一样
  • SqlSession 相同, 两次查询直接执行了增删改查操作, 因为这次操作可能会对当前数据库有影响
  • SqlSession 相同, 手动清除了一级缓存(openSession.clearCache()

这些情况就不一一举例了,以后用到关注一下即可

2. 使用二级缓存

EmployeeMapper 有一个二级缓存区域(按 namespace 分),其它 Mapper 也有自己的二级缓存区域(按 namespace 分)。每一个 namespace 的 mapper 都有一个二级缓存区域,两个 mapper 的 namespace 如果相同,这两个 mapper 执行 sql 查询到数据将存在相同的二级缓存区域中。

MyBatis | 一级缓存与二级缓存_第5张图片

2.1 工作机制

  1. 一个会话, 查询一条数据, 这个数据就会被保存在当前会话的一次缓存(SqlSession)中
  2. 如果关闭会话, 那么一次缓存中的数据就会被保存到二级缓存(namespace)中,新的会话查询的内容, 就可以参照二级缓存
  3. 一个 xxxMapper 对应一个 namespace, 不同的 namespace 查出的数据会保存在自己对应的二级缓存中
  4. 查出的数据会先被保存在一级缓存中, 只有会话关闭或者提交之后, 以及缓存中的数据才会被转移到二级缓存

2.2 开启二级缓存步骤

  1. 开启全局二级缓存配置
  2. 去 mapper.xml 中配置使用二级缓存
  3. 我们的 POJO 需要实现序列化接口

在全局文件中开启二级缓存


    

在 sql 映射文件中配置使用二级缓存,具体参数可以去官方文档查询


    
    ...

实现序列化

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", 表示增删改查操作完成之后, 会自动清除缓存, 此时一级缓存和二级缓存都会被清空

三、参考

深入理解MyBatis中的一级缓存与二级缓存
【MyBatis学习13】MyBatis中的二级缓存

你可能感兴趣的:(MyBatis | 一级缓存与二级缓存)