MyBatis之一级缓存、二级缓存
1、一级缓存
跟踪BaseExecutor可以看到,在query方法中,实现了缓存逻辑,当缓存不存在的时候,则调用实现类中的doQuery。
创建一级缓存的KEY:
查看缓存中是否存在,存在则直接返回,不存在则查询数据库:
查询数据库:
这就是Mybatis中的一级缓存,逻辑十分简单,可以从源码中看到,一级缓存是默认开启的并且在同一个线程中有效,同时查询完毕之后,立即建立缓存。
1)、一级缓存命中场景
mybatis中的一级缓存是存放在hashMap中的,之所以没有用线程安全的集合,主要是因为SqlSession都是线程不安全的,所以没有必须要。
1、运行时命中相关
① 同一个会话中(所以也被成为会话级缓存)
② Sql语句、参数相同
③ 相同的statementId(相同Sql和参数,在不同的statementId中也不行)
④ RowBounds相同(mybatis分页使用,可以理解为参数相同)
2、操作和配置相关
① 未手动清空缓存(session1.clearCache();)
② 未配置 @Options(flushCache = Options.FlushCachePolicy.TRUE),其实原理同上(在实验中发现必须要跟@Select标签一起使用才能生效)
③ 未执行 Update 语句(在源码中可以看到,只要更新操作就会调用 clearCache()方法)
④ 作用域为STATEMENT(在setting中配置
⑤ 使用事务提交、回滚操作
总结:
1、会话相关
2、参数条件相关
3、提交、事务、更新会清空
2)、一级缓存失效场景
在spring和mybaits中,会出现mybatis一级缓存失效的情况,主要原因是spring的封装,导致每次查询都会创建一个SqlSession,因为一级缓存是会话级缓存,所以导致失效。通过分析源码可知,在同一个事务情况下,spring 不会重新创建SqlSession,所以开启事务即可解决。
spring调用mybatis流程如下:
这里可以看到,在SqlSessionTemplate中是使用sqlSession代理进行调用的
继续追踪下去可以看出来,sqlSessionProxy代理是使用 动态代理 SqlSessionInterceptor 创建的,
下面查看 SqlSessionInterceptor 中获取SqlSession方法:
到这里获取到的SqlSession,就是执行sql的SqlSession
可以看到他其实从ThreadLocal(也就是一个事务中保存的SqlSession)变量中先获取了一下,是否存在 ,存在直接使用,不存在就重新创建。这也就是为什么开启事务就能命中一级缓存的主要原因。
可以看到sqlSession被spring中的 SqlSessionTemplate替换了,但是 SqlSessionTemplate 本身没有执行Sql的能力,在其中又采用动态代理获取了DefaultSqlSession。
2、二级缓存
mybatis中,实现二级缓存是使用CachingExecutor这个类实现的,同时采用装饰者模式,对查询进行调用,源码如下:
在CachingExecutor中,存在一个属性就是 Executor,而CachingExecutor本身只实现缓存相关的内容:
上面的源码可以看到,当缓存开启的时候,先查询缓存中是否存在,不存在则使用执行器(Executor)进行查询。
下面查看二级缓存怎么启动:
1、在setting中配置缓存开启:
2、在需要开启缓存的Mapper.xml文件中添加如下一行:
编写例子 :
虽然使用的 CachingExecutor 但是发现日志输出的缓存命中率是0,这是因为二级缓存需要提交之后才能建立缓存,打开cachingExecutor.commit(ture)
在平时开发的过程中,其实直接使用SqlSession进行sql的执行的,并不需要上面那么复杂的代码。
其他配置内容:
PS:
① flushCache 清空之后,需要提交才能生效,并且只要提交了,所有的缓存都会被清空。
②
跟踪代码可知,
当开启缓存的时候,可以看到是先执行二级缓存,再执行一级缓存的。
1)、二级缓存存在的意义
1、二级缓存是跨线程的
2、一级缓存的作用范围相对小,二级缓存的生命周期为整个应用
2)、二级缓存的需求
1、存储的多元性(Redis、Mysql、内存等)
2、存储容量需要限定,就存在淘汰规则(FIFO[先进先出]、LRU[最近最少使用])
3、数据的过期清理
4、保证线程安全
5、缓存的命中率统计
6、序列化
3)、二级缓存组件的结构
mybatis二级缓存逻辑实现采用:装饰器+责任链的方式进行实现的。
其实也就是每个上述的需求点,都会对应一个集成Cache接口的类,并且这些类串联起来,最终实现缓存的逻辑。
debug可以看到如下:
4)、二级缓存命中条件
① 必须提交之后(就算sqlsession是自动提交的,也需要手动提交才能生效)
② Sql语句、参数相同
③ statementId 相同
④ 分页条件相同
为什么提交之后才能命中缓存?
主要是因为,二级缓存是跨线程访问的,为了防止其他线程的脏读。
每个会话都有自己的事务缓存管理,当会话读取的数据的时候,会暂时存放到事务管理器的暂存区中,只有commit之后,才提交到缓存区中,这个时候才能被其他的会话共享。不论是查询还是更新都是暂时放在暂存区中。
其中暂存区的多少取决于使用了多少个命名空间。
执行流程:
下面的源码可以看到,采用一个标志位clearOnCommit,进行维护,主要是因为,如果另一个会话对缓存的数据进行了update,但是没有commit的时候,update的数据值存在暂存区中。
以上是对mybatis的执行器和缓存机制的简单描述。
这里推荐mybatis讲解非常棒的福利:B站 传送门 真正的干活满满!~
同时盗用一下UP主的图片: