Mybatis二级缓存源码剖析

⼆级缓存构建在⼀级缓存之上,在收到查询请求时,MyBatis 首先查询⼆级缓存,若⼆级缓存未命 中,再去查询⼀级缓存,⼀级缓存没有,再查询数据库。

与⼀级缓存不同,⼆级缓存和具体的命名空间绑定,⼀个Mapper中有⼀个Cache,相同Mapper中的 MappedStatement公用⼀个Cache,⼀级缓存则是和 SqlSession 绑定

一,全局二级缓存开启配置

<settings>
 <setting name="cacheEnabled" value="true"/>
</settings>

方向:通过配置开启二级缓存,源码剖析从配置读取生效入手

还是如同Mybatis之前的解析步骤一样,从Mybatis初始化读取配置入手,进入到XMLConfigureBuilder类中的parse方法。

然后继续往下层去剥,根据上面的开启全局二级缓存配置,找到parseConfiguration中的解析Setting标签的部分。

 二,Mapper中的Cache标签解析与对象构建

全局二级缓存开启之后,对于使用二级缓存的Mapper.xml需要在Mapper标签内进行如下的配置:

<cache></cache>。

从配置中可以知道,二级缓存绑定Mapper,真正去调用绑定应该还是在Mapper标签解析的时候去执行的。承接上面的解析代码进到mapperElement方法当中去,然后判断是指根据何种方式去读取的Mapper配置加载方式。读取的方式不会决定Mapper读取后处理的步骤,这里从路径配置的else条件中parse方法进入。

 一直往下走到这里为止,则看到了Mapper解析时,解析cache标签的地方了。上面的cache-ref表示的是从别的地方去取缓存数据,这里不做多的深入探究,继续看cacheElement方法。

 进到cacheElement中后,可以看到对于cache标签,还提供了其它的属性配置。 上面部分的属性配置读取完成后,最后useNewCache则开始创建Cache对象,点进去看如何去构建的。

 进入到方法后,第一步实例化一个Cache对象,把之前的配置信息放入到对象中。然后第二步,是否是似曾相识?把构建好的Cache对象存入到Configuration对象当中去。之前的解析主线源码的时候有说过这个对象,解析出来的内容放入到Configuration对象然后一直向下传递。第三步则是把构建的新的二级缓存对象复制给buildAssistant类中的currentCache。

 到这里为止,可以发现对于一个Mapper Cache对象只在初始化时候创建一次,符合⼀个Mapper中有⼀个Cache对象。但是相同的Mapper中公用一个Cache对象,之前主线源码解析有说过Mapper对象在解析后会整合了存入到Configuration中的MappeStatement容器中去,那么对于相同的Mapper读取整合后,Cache对象是否应该去绑定进去?

回到之前Mapper解析的方法中去,从buildStatementFromContext方法进去,这个方法之前有提到过,对于整合解析好的Mapper会存入到MappeStatement当中去。然后对于判断不论如何都会去调用buildStatementFromContext方法,直接点进该方法。

 然后看到又构建了一个XMLBuilder,然后去解析MappeStatement,这里的代码之前主线解析也有提到过。进入到parseStatementNode方法,重要看关于cache的部分,之前该方法的完整部分也有在主线解析时贴出来,这里直接看重点的几行代码。

flushCache都不陌生,对于每次数据库操作是否都去刷新当前缓存,后面的useCache则是是否使用二级缓存.

然后该方法往下拉,到构建MappeStatement,注意这里的对象buildAssistant,上面有提到过该对象,对于创建的Cache对象第三步,会赋值给buildAssistant类中的currentCache。

 进入到该方法之后,对于之前的CurrentCache对象,会在MappeStatement构建的过程中进行绑定。 到这里位置Cache标签的解析,Cache对象的构建,传入与绑定则全部剖析完成。

三,二级缓存执行流程剖析

SQL执行的过程中如何从二级缓存中存入和拿取数据?既然涉及了SQL的执行,那么从直接从SqlSession中的常用数据操作方法入手,这里就从SqlSession中的SelectList入手。(为什么不从selectOne?selectOne底层也是并入到selectList中去委派Excutor执行的)

 通过Debug模式可以发现,在开启了二级缓存后,executor的执行方式和之前主线源码解析的不同了,它会跳转到CachingExecutor中去委派执行SQL操作。

 进入到CachingExecutor中的query方法,BoundSql对于参数#{}与?的转换,参数的传入,之前主线代码解析有提到过,这里不多做分析,这里创建了一个CacheKey,然后去执行查询的操作。

 进入到query方法后,注意几个方面:

1,cache标签上面解析最后的步骤时,会对于MappedStatement进行绑定,确定相同的MappeStatement公用一个Cache对象,这里从MappedStatement中获取Cache对象

2,上面有提到过的Cache标签中还能够去配置的一些参数,比如是否有用到二级缓存的UseCache,Mapper解析时,是否每次操作都要清空缓存的数据flushCache。

3,这里的tcm是一个二级缓存的事务管理器,它的作用就是从二级缓存的中去取数据,因为是第一次操作,list肯定是空的,那么走到下面的delegate

4,delegate可以看到是一个excutor,它负责去执行做SQL操作,这里的executor通过Debug去执行,会发现它跳转到了BaseExecutor中去,如同主线执行一样,会从一级缓存中去拿结果。

 但是第一次执行肯定一级缓存中也没有数据,那么则会从数据库中去拿数据,然后存入到本地缓存,也就是所谓的一级缓存。

 数据存入到了一级缓存中了,上面的代码中有这样一个存放数据的操作,这个操作时进行二级缓存吗?

点进去后到TransactionalCacheManager对象,对象中对Cache作为Key去再封装了一层,这里为什么不直接使用Cache对象呢?Cache对象在一个MappeStatement中的唯一性,如果直接获取,多请求并发时会导致线程的不安全性,所以把缓存的数据放入到了一个TransactionCache中去。

 继续往下走,到TransactionalCache对象中,这里把查询的数据存入到了该对象的Set集合中,那么问题来了,到这里为止,数据都没有存入到二级缓存中去,这里的存入到Set中是因为事务性,回滚操作下防止二级缓存的频繁存取。

在TransactionalCache中可以看到commit方法,二级缓存只对提交的事务会写入。commit中,delegate是否似曾相识?之前二级缓存的获取,就是从tcm的get方法,就从这里获取的,它就是一个二级缓存的对象。

 然后去看flushPendingEntries方法,entriesToAddOnCommit这个容器已经提到过了,这里从容器中获取未提交的容器中的数据存入到二级缓存。到这里为止,可以发现二级缓存对于没有提交的数据是不具备缓存作用的,如果没有提交,delegate缓存对象永远是一个null值,永远拿不到数据。

四,二级缓存的刷新机制 

二级缓存的刷新无非在于SQL进增删改操作时的缓处理。还是SqlSession当中去,去看非查询的操作,这里从更新操作着手。还是会判断update标签中是否加入了flushCache属性。update操作之前都会去执行一次select操作,第一次执行flush方法时候,利用debug跟踪,因为下面的参数为false不会去清空二级缓存,但是在查询之后会对该参数重新赋值为true,在第二次执行时,这里的判断就是true了,则会情空二级缓存。

然后继续往下走,到BaseExecutor中去,到这里更新完会清空本地的缓存数据,也就是一级缓存的数据,然后执行doUpdate方法完成更新。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值