二级缓存源码剖析

二级缓存构建在一级缓存之上,在收到查询请求时,MyBatis 首先会查询二级缓存,若二级缓存未命中,再去查询一级缓存,一级缓存没有,再查询数据库。
二级缓存———》 一级缓存———》数据库
与一级缓存不同,二级缓存和具体的命名空间绑定,一个Mapper中有一个Cache,相同Mapper中的
MappedStatement共用一个Cache,二级缓存则是和 SqlSession 绑定。

启用二级缓存

需要完成如下三步开始开始启动:

1) 开启全局二级缓存配置:

在当前的数据库配置文件中添加配置

  1. <settings>
  2. <setting name="cacheEnabled" value="true"/>
  3. </settings>

2) 在需要使用二级缓存的Mapper配置文件中配置标签

cache里面有很多属性,但是目前是走默认值

<cache></cache>

3) 在具体CURD标签上配置 useCache=true

其实默认值就是true,也就是给sql的配置文件进行配置

<select id="findById" resultType="com.lagou.pojo.User" useCache="true">
     select * from user where id = #{id}
 </select>

4) 如何使用二级缓存

   InputStream inputStream = Resources.getResourceAsStream("sqlMapConfig.xml");
    SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);

    SqlSession sqlSession1 = factory.openSession();
    SqlSession sqlSession2 = factory.openSession();

    User user1 = sqlSession1.selectOne("com.lagou.mapper.IUserMapper.findById", 1);
    System.out.println(user1);

    //二级缓存的生效是在事物的commit/close之后,第一次查的结果才会存到缓存中。
    //当第二次查询sql就不会向数据库发送查询语句。直接从缓存拿到结果
    sqlSession1.commit();

    User user = new User();
    user.setId(1);
    user.setUsername("jack");
    // 如果存在增删改会清空二级缓存
    sqlSession1.update("com.lagou.mapper.IUserMapper.updateById",user);

    // 缓存被清空 ,再发送查询请求,还是会到数据库查询。
    User user2 = sqlSession2.selectOne("com.lagou.mapper.IUserMapper.findById", 1);
    System.out.println(user2);

5) 分析如何实现的

第一和第三步都是默认为true,配不配置都问题不大,那么第二步一定要配置。
那么它是如何解析的呢?又如何产生cache对象呢?

问题1: 标签 < cache/> 的解析

根据之前的mybatis源码剖析,xml的解析工作主要交给XMLConfigBuilder.parse()方法来实现
因为cache是配置在sql配置文件里面(也就是对应的mapper配置文件中),那么也就是对sql配置文件解析的时候解析它的。(解析每一个语句的时候,都会把生成的缓存cache添加到对应的预处理javabean中,只要是同一个mapper的解析出来都保存有同一个cache) 源码如下:

1、
image.png
2、
image.png

3、
image.png

4、
image.png

5、
image.png

6、
image.png

7、
image.png

8、
image.png

9、
image.png

10、
image.png

到一个Mapper.xml只会解析一次标签,也就是只创建一次Cache对象,放进configuration中, 并将cache赋值给MapperBuilderAssistant.currentCache

11、
image.png

12、
image.png

13、
image.png

14、
image.png

问题2:查询源码剖析

一级缓存、二级缓存同时开启的状态下:
查询是先从二级缓存查询、再到一级、在到数据库,数据库查到的结果放到一级缓存,而查询二级缓存是从另一个对象中获取的,一级的数据不是立刻放到二级,而是通过事务管理器,将其刷到缓存中的。所以第二次查询才会从缓存中得到。具体如下:

1、
image.png

2、
image.png

3、
image.png

4、
image.png

5、
image.png

6、
这里会调用CachingExecutor,为什么呢?请看数据库配置文件,里面配置
image.png
image.png

7、
image.png

8、
image.png

9、点击二级缓存
image.png

10、点击看如何获取
image.png

10、
image.png

11、如果上面二级缓存没有命中,则到一级缓存查询
image.png

12、继续调用实现类
二级缓存没有数据,就相当于二级缓存不用了,这么理解。然后去找一级缓存,就是BaseExecutor类中的。
image.png

13、继续从一级缓存获取,没有就到数据库获取。
image.png

14、如果一级缓存没有,则到数据库查询
image.png

15、

protected PerpetualCache localCache;
image.png

16、注意:上面数据库获取结果先到一级缓存,但是取呢是先从二级缓存取的
那么一级缓存是什么是否放到二级缓存的呢?
点进去看
image.png

17、
image.png

18、那么问题又来了,那什么时候才会将它从map中转到二级缓存呢?
先保留这个疑问!

问题三:为什么要提交或者关闭事物二级缓存才生效呢?

1、
image.png

2、
image.png

3、
image.png

4、配置有二级缓存是,提交事物时候,会走cache
image.png

5、
image.png

6、
image.png

7、
image.png

8、
到这里也就能解析问题2的18问了!
image.png

9、为什么说事物close也会刷呢
image.png

10、
image.png

11、
image.png

12
image.png

13、这个是和问题三的第6点是一样的。调用里面的commit方法
image.png

14、增删改操作都会先清空缓存
image.png

MyBatis二级缓存只适用于不常进行增、删、改的数据,比如国家行政区省市区街道数据。一但数据变更,MyBatis会清空缓存。因此二级缓存不适用于经常进行更新的数据。

总结:

在二级缓存的设计上,MyBatis大量地运行了装饰者模式,如CachingExecutor, 以及各种Cache接口的装饰器。
二级缓存实现了Sqlsession之间的缓存数据共享,属于namespace级别
二级缓存具有丰富的缓存策略。
二级缓存可由多个装饰器,与基础缓存组合而成
二级缓存工作由一个缓存装饰执行器CachingExecutor和一个事务型预缓存TransactionalCache完成。