二级缓存源码剖析
二级缓存构建在一级缓存之上,在收到查询请求时,MyBatis 首先会查询二级缓存,若二级缓存未命中,再去查询一级缓存,一级缓存没有,再查询数据库。
二级缓存———》 一级缓存———》数据库
与一级缓存不同,二级缓存和具体的命名空间绑定,一个Mapper中有一个Cache,相同Mapper中的
MappedStatement共用一个Cache,二级缓存则是和 SqlSession 绑定。
启用二级缓存
1) 开启全局二级缓存配置:
在当前的数据库配置文件中添加配置
<settings>
<setting name="cacheEnabled" value="true"/>
</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、
2、
3、
4、
5、
6、
7、
8、
9、
10、
到一个Mapper.xml只会解析一次标签,也就是只创建一次Cache对象,放进configuration中, 并将cache赋值给MapperBuilderAssistant.currentCache
11、
12、
13、
14、
问题2:查询源码剖析
一级缓存、二级缓存同时开启的状态下:
查询是先从二级缓存查询、再到一级、在到数据库,数据库查到的结果放到一级缓存,而查询二级缓存是从另一个对象中获取的,一级的数据不是立刻放到二级,而是通过事务管理器,将其刷到缓存中的。所以第二次查询才会从缓存中得到。具体如下:
1、
2、
3、
4、
5、
6、
这里会调用CachingExecutor,为什么呢?请看数据库配置文件,里面配置
7、
8、
9、点击二级缓存
10、点击看如何获取
10、
11、如果上面二级缓存没有命中,则到一级缓存查询
12、继续调用实现类
二级缓存没有数据,就相当于二级缓存不用了,这么理解。然后去找一级缓存,就是BaseExecutor类中的。
13、继续从一级缓存获取,没有就到数据库获取。
14、如果一级缓存没有,则到数据库查询
15、
protected PerpetualCache localCache;
16、注意:上面数据库获取结果先到一级缓存,但是取呢是先从二级缓存取的
那么一级缓存是什么是否放到二级缓存的呢?
点进去看
17、
18、那么问题又来了,那什么时候才会将它从map中转到二级缓存呢?
先保留这个疑问!
问题三:为什么要提交或者关闭事物二级缓存才生效呢?
1、
2、
3、
4、配置有二级缓存是,提交事物时候,会走cache
5、
6、
7、
8、
到这里也就能解析问题2的18问了!
9、为什么说事物close也会刷呢
10、
11、
12
13、这个是和问题三的第6点是一样的。调用里面的commit方法
14、增删改操作都会先清空缓存
MyBatis二级缓存只适用于不常进行增、删、改的数据,比如国家行政区省市区街道数据。一但数据变更,MyBatis会清空缓存。因此二级缓存不适用于经常进行更新的数据。
总结:
在二级缓存的设计上,MyBatis大量地运行了装饰者模式,如CachingExecutor, 以及各种Cache接口的装饰器。
二级缓存实现了Sqlsession之间的缓存数据共享,属于namespace级别
二级缓存具有丰富的缓存策略。
二级缓存可由多个装饰器,与基础缓存组合而成
二级缓存工作由一个缓存装饰执行器CachingExecutor和一个事务型预缓存TransactionalCache完成。