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 清空之后,需要提交才能生效,并且只要提交了,所有的缓存都会被清空。

和 @CacheNamespace 不是一个缓存空间,也就是使用@Select 期望命中缓存,则需要使用 @CacheNamespace,而xml中期望命中缓存,则需要使用,这里是需要注意的。

跟踪代码可知,

当开启缓存的时候,可以看到是先执行二级缓存,再执行一级缓存的。

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主的图片:

posted @ 2023-06-02 17:13  戒爱学Java  阅读(374)  评论(0编辑  收藏  举报