mybatis缓存分类及实现方法(深入Mybatis缓存)
mybatis缓存分类及实现方法(深入Mybatis缓存)从Mybatis官方文档中我们得知,Mybatis缓存分一级缓存和二级缓存两种。这一句话粗一看比较容易理解,逻辑也比较简单。但是咱们咬文嚼字来仔细研读一番,不难发现其实内心还是有不少疑问。比如说缓存的数据以什么形式存储,存储在哪里,缓存能存储多少数据量,当数据容量超过后怎么办,当数据更新后缓存失效了又改如何处理,再比如Mybatis是如何界定“the same SQL STATEMENT ”呢?带着这些疑问,我们决定从源码角度进行深入的分析。不过各位客官稍安勿躁,在深入分析源码前,我们先来看看应用开发者如何使用Mybatis缓存让我们系统快得飞起。常见的应用内缓存,分布式缓存(redis)均属于缓存。今天我们主要分析的Mybatis持久层Cache就属于应用内缓存。什么是Mybatis缓存引用Mybatis官方的解释,Mybatis缓存意味着当Mybatis执行了SQL查询或更新后,SQL
深入mybatis缓存
接着上一篇文章<<Mybatis缓存与Mysql“读已提交”隔离级别冲突>>,今天咋们来对mybatis缓存一探究竟,笔者准备从以下几个纬度进行分析。
- 什么是缓存以及意义
- 什么是Mybatis缓存
- 源码角度分析
- 现实应用场景
- 存在的问题
什么是缓存及其意义
从上图描述可以得知,缓存是内存里的一块DB查询结果的数据对象,其目的在于减少与DB的交互降低DB压力改善相应速度。
常见的应用内缓存,分布式缓存(redis)均属于缓存。今天我们主要分析的Mybatis持久层Cache就属于应用内缓存。
什么是Mybatis缓存
引用Mybatis官方的解释,Mybatis缓存意味着当Mybatis执行了SQL查询或更新后,SQL Statment不会立马消失,而是缓存到内存,当同一个SQL再次执行,将会直接从缓存返回结果而无需再次查询数据库。
这一句话粗一看比较容易理解,逻辑也比较简单。但是咱们咬文嚼字来仔细研读一番,不难发现其实内心还是有不少疑问。比如说缓存的数据以什么形式存储,存储在哪里,缓存能存储多少数据量,当数据容量超过后怎么办,当数据更新后缓存失效了又改如何处理,再比如Mybatis是如何界定“the same SQL STATEMENT ”呢?带着这些疑问,我们决定从源码角度进行深入的分析。不过各位客官稍安勿躁,在深入分析源码前,我们先来看看应用开发者如何使用Mybatis缓存让我们系统快得飞起。
从Mybatis官方文档中我们得知,Mybatis缓存分一级缓存和二级缓存两种。
一级缓存
在MyBatis的配置文件中,添加如下语句,就可以使用一级缓存。共有两个选项,SESSION或者STATEMENT,默认是session级别,即在一次会话中执行的所有语句,都会共享这一个缓存。一种是STATEMENT级别,可以理解为缓存只对当前执行的这一个Statement有效。
<setting name="localCacheScope" value="SESSION"/>
从如下执行结果可以清楚地看到,真正的数据库卡查询其实只执行了一次,缓存起作用了,在同一个SqlSession作用域只查询了一次。
紧接着我们再来看一下当我们新开一个SqlSession,再次执行同样的查询是什么效果,理论上应该出现两次DB查询
下面的结果验证了我们的理论,进行了两次DB查询
那么如果在同一个会话中,第一次读取数据后,紧接着对对应的数据做修改后,缓存会失效吗?
从下图结果可以看出,数据更新后再次查询数据缓存已失效,需要再次从DB读取。
注意了,下面这个场景也许会坑到你
会话读取了一条记录并缓存到内存,此时session1对该记录进行了更新,旧的会话再次读取数据会发现仍然是缓存的数据,如下图所示,如果是在(RC)隔离级别下就会出现读到脏数据的情况,你的业务场景能接受吗?如果不能,那么建议你设置scope为STATEMENT,这样每次查询结束都会清掉一级缓存如下图所示。
<setting name="localCacheScope" value="STATEMENT"/>
scope为STATEMENT场景
上面通过实际的案例演示了一级缓存的使用,但是对前面咋们抛出的几个问题并没有得到解决,下面我们将带着这些疑问去一一分析。
Cache如何存储,存储在内存的缓存可能造成内存溢出吗?
Cache什么时候创建?
同一session作用域内数据被更新后缓存会失效吗?
在回答这些问题前,先一起来看一下Mybatis执行流程
SqlSession将查询请求委托给excutor(CacheExcutor)
注意上图的Cachkey这个类了吗,猜猜他是干什么的。对头这个就是唯一标识一次查询对应的唯一标识key,也即Mybatis如何去标识是相同的查询的依据。看一下CacheKey的定义
Cachekey是如何被创建的,List<Object>数组到底存放哪些值呢?
从上图可以看出主要由5个参数计算hashcode的值,因此同一个查询语句当你传入相同的参数及limit和offset值才有可能能命中缓存。当environment不为空时,该值也是一个决定因素。
下图首先从缓存获取值,如果不存在则调queryFromDatabase从数据库获取数据。
queryFromDatabase则负责从DB获取数据并更新缓存。
关于缓存的写入已经讲清楚了。那么当数据被更新后,如何去保证读取最新的数据而不至于出现脏读呢?如果是我们自己该如何去设计呢?
我觉得有两种思路,第一种思路就是数据有更新的情况下(Sqlsession的update delete方法被调用)直接将缓存失效,下次查询时候再从DB读取并写入缓存,另外一种思路就是发现如果存在对应缓存数据直接将缓存进行更新。但是仔细想想其实你会发现第二种思路看起来很好用一步到位,但个人觉得这会牵涉到事务的一些处理,如会话只是做了update操作但是后续rollback了。按照这种思路咱们再来分析一下
SqlSession的update和delete方法,看其是不是会涉及到缓存相关操作。
再深入到clearLocalCache的实现你会发现这里最终调用了Map的clear方法将Map内容清空,这里直接采用了比较粗暴的方式将会话关联的一级缓存直接清空,而不是精准的清空,笔者认为应该是在实现成本和价值之间做了平衡。也即是说只要做了更新操作,而不管你是操作的什么数据,缓存都会失效,这个和我们平时做精准失效还是有一些出入,使用时需要注意。如果下面的场景不会应用到缓存
至此Mybatis一级缓存咋们算是有里清晰的认识了。下一遍文章将深入分析Mybatis二级缓存的流程。
如果大家有不同的见解,欢迎在评论区留言讨论。