1.MyBatis的缓存机制整体设计以及二级缓存的工作模式
如上图所示,当开一个会话时,一个SqlSession对象会使用一个Executor对象来完成会话操作,MyBatis的二级缓存机制的关键就是对这个Executor对象做文章。如果用户配置了"cacheEnabled=true",那么MyBatis在为SqlSession对象创建Executor对象时,会对Executor对象加上一个装饰者:CachingExecutor,这时SqlSession使用CachingExecutor对象来完成操作请求。CachingExecutor对于查询请求,会先判断该查询请求在Application级别的二级缓存中是否有缓存结果,如果有查询结果,则直接返回缓存结果;如果缓存中没有,再交给真正的Executor对象来完成查询操作,之后CachingExecutor会将真正Executor返回的查询结果放置到缓存中,然后在返回给用户。
CachingExecutor是Executor的装饰者,以增强Executor的功能,使其具有缓存查询的功能,这里用到了设计模式中的装饰者模式,
CachingExecutor和Executor的接口的关系如下类图所示:
2 . MyBatis二级缓存的划分
MyBatis并不是简单地对整个Application就只有一个Cache缓存对象,它将缓存划分的更细,即是Mapper级别的,即每一个Mapper都可以拥有一个Cache对象,具体如下:
a.为每一个Mapper分配一个Cache缓存对象(使用<cache>节点配置);
b.多个Mapper共用一个Cache缓存对象(使用<cache-ref>节点配置);
3. 使用二级缓存,必须要具备的条件
MyBatis对二级缓存的支持粒度很细,它会指定某一条查询语句是否使用二级缓存。
虽然在Mapper中配置了<cache>,并且为此Mapper分配了Cache对象,这并不表示我们使用Mapper中定义的查询语句查到的结果都会放置到Cache对象之中,我们必须指定Mapper中的某条选择语句是否支持缓存,即如下所示,在<select>节点中配置useCache="true",Mapper才会对此Select的查询支持缓存特性,否则,不会对此Select查询,不会经过Cache缓存。如下所示,Select语句配置了useCache="true",则表明这条Select语句的查询会使用二级缓存。
- <select id="selectByMinSalary" resultMap="BaseResultMap" parameterType="java.util.Map" useCache="true">
总之,要想使某条Select查询支持二级缓存,你需要保证:
1. MyBatis支持二级缓存的总开关:全局配置变量参数 cacheEnabled=true
2. 该select语句所在的Mapper,配置了<cache> 或<cached-ref>节点,并且有效
3. 该select语句的参数 useCache=true
4. 一级缓存和二级缓存的使用顺序
请注意,如果你的MyBatis使用了二级缓存,并且你的Mapper和select语句也配置使用了二级缓存,那么在执行select查询的时候,MyBatis会先从二级缓存中取输入,其次才是一级缓存,即MyBatis查询数据的顺序是:
二级缓存 ———> 一级缓存——> 数据库
5. 二级缓存实现的选择
MyBatis对二级缓存的设计非常灵活,它自己内部实现了一系列的Cache缓存实现类,并提供了各种缓存刷新策略如LRU,FIFO等等;另外,MyBatis还允许用户自定义Cache接口实现,用户是需要实现org.apache.ibatis.cache.Cache接口,然后将Cache实现类配置在<cache type="">节点的type属性上即可;除此之外,MyBatis还支持跟第三方内存缓存库如Memecached的集成,总之,使用MyBatis的二级缓存有三个选择:
1.MyBatis自身提供的缓存实现;
2. 用户自定义的Cache接口实现;
3.跟第三方内存缓存库的集成;
6. MyBatis自身提供的二级缓存的实现
MyBatis自身提供了丰富的,并且功能强大的二级缓存的实现,它拥有一系列的Cache接口装饰者,可以满足各种对缓存操作和更新的策略。
MyBatis定义了大量的Cache的装饰器来增强Cache缓存的功能,如下类图所示。
对于每个Cache而言,都有一个容量限制,MyBatis各供了各种策略来对Cache缓存的容量进行控制,以及对Cache中的数据进行刷新和置换。MyBatis主要提供了以下几个刷新和置换策略:
LRU:(Least Recently Used),最近最少使用算法,即如果缓存中容量已经满了,会将缓存中最近做少被使用的缓存记录清除掉,然后添加新的记录;
FIFO:(First in first out),先进先出算法,如果缓存中的容量已经满了,那么会将最先进入缓存中的数据清除掉;
Scheduled:指定时间间隔清空算法,该算法会以指定的某一个时间间隔将Cache缓存中的数据清空;
6. 写在后面(关于涉及到的设计模式)
在二级缓存的设计上,MyBatis大量地运用了装饰者模式,如CachingExecutor, 以及各种Cache接口的装饰器。关于装饰者模式,读者可以阅读相关资料,我的另外一篇博文 Java 设计模式 装饰者模式供读者参考。
本文只是讲述MyBatis二级缓存的基本原理,关于自定义二级缓存和与第三方内存库的集成,将在后续的文章中再做讨论,敬请关注!
-----------------------------------------------------------------------------------------------------------------------------------------
本文源自 http://blog.csdn.net/luanlouis/,如需转载,请注明出处,谢谢!
-
顶
- 50
-
踩
- 0
14楼 Coder乱码 2017-09-15 16:20发表 [回复]-
-
不错不错,收藏了。
推荐下,分库分表中间件 Sharding-JDBC 源码解析 17 篇:http://www.yunai.me/categories/Sharding-JDBC/?csdn&201
抽
13楼 forsumx 2017-01-19 11:21发表 [回复]-
-
您好,我在实际测试源码的时候,发现CachingExecutor是在任何情况下都会包装Executor对象的。无论全局是否开启了cacheEnable策略。这个CachingExecutor是一级缓存的包装还是二级缓存的包装类?
12楼 YEN_CSDN 2016-10-14 22:25发表 [回复]-
-
挺详细的
11楼 仰望星空_Star 2016-10-05 15:11发表 [回复]-
-
还有个问题,<cache-ref namespace="org.mapper.CostMapperDao"></cache-ref>
这样配置了后好像也没有生效,无法使用CostMapperDao的缓存,后来发现是cacheKey不一样,对比了一下发现是二者的MappedStatement ID不同
10楼 仰望星空_Star 2016-10-04 18:25发表 [回复]-
-
就是配置了flushCache=true之后useCache好像就无效了
9楼 仰望星空_Star 2016-10-04 18:06发表 [回复]-
-
有个问题请教下,一个查询语句如果使用了flushCache=true useCache=true后为什么还是会执行第二次查询,看日志它会获取缓存对象然后再次执行查询,如果flushCache=false就会命中缓存,不会进行二次查询 ,请问这是什么原因?
8楼 zbp1025 2015-09-06 20:04发表 [回复]-
-
Executor是装饰者模式的话 baseExecutor应该是装饰者吧
7楼 剑八- 2015-05-25 17:01发表 [回复]-
-
你好,
二级缓存个人觉得,如果对应用的是mybatis自身的或者第三方类似本地形式的cache,这样会有点问题
例:
商品服务有两台机子A和B,对于查询语句:
select * from product where id=1来讲。
第一次查询商品服务机子A,发现二级缓存没有,这时从一级缓存及数据库中进行查找,最终将查询到的结果放入二级缓存
第二次,如果用户对于id为1的商品进行更新,更新操作有可能在商品机子A或B上,这时是否就会产生数据不一致情况?
6楼 剑八- 2015-05-25 17:01发表 [回复]-
-
你好,
二级缓存个人觉得,如果对应用的是mybatis自身的或者第三方类似本地形式的cache,这样会有点问题
例:
商品服务有两台机子A和B,对于查询语句:
select * from product where id=1来讲。
第一次查询商品服务机子A,发现二级缓存没有,这时从一级缓存及数据库中进行查找,最终将查询到的结果放入二级缓存
第二次,如果用户对于id为1的商品进行更新,更新操作有可能在商品机子A或B上,这时是否就会产生数据不一致情况?
5楼 干净的句号 2015-04-14 16:11发表 [回复]-
-
楼主,对于,cache-ref,我有一个问题。我知道是全局共享的。但是全局共享,也没有什么作用啊,比如
mapperA.methodA(); 缓存到mapperA空间 key值是mapperA
mapperB.methodB();缓存到mapperB空间 key值是mapperB
mapperC.methodC();缓存到mapperC空间 key值是mapperC
现在都同一缓存到一个空间mapperD空间,作用还是一样呀!还是,无非就是少了几个namespace而已
4楼 GaryArch 2015-01-12 14:58发表 [回复]-
-
我想问一下,如果系统需要根据登录用户名,链接至不同的数据库。我比较担心数据出现混乱。 比如 用户名A 查询到用户B的数据。
我的理解是,一级缓存,还是可以用。因为一级缓存的作用范围是sqlsession级别。
默认的二级缓存应该不能用。
是否可以通过自己扩展mybatis cache. 将数据库id添加到cache key中 ?
Re: 干净的句号 2015-04-14 16:06发表 [回复]-
-
回复GaryArch:这个需要切换数据库,跟缓存机制没有关系。
3楼 fj359941160 2014-12-07 16:13发表 [回复]-
-
用了二级缓存,开了两个SqlSession,没起作用。
Re: 亦山 2014-12-07 16:24发表 [回复]-
-
回复fj359941160:另外,执行查询后,执行SqlSession.close(),也会将查询结果真正提交到二级缓存处理……
Re: 亦山 2014-12-07 16:17发表 [回复]-
-
回复fj359941160:执行过查询之后,只有执行sqlSession.commit()后才会将查询结果真正提交到二级缓存处理
2楼 bowengod 2014-12-03 10:32发表 [回复]-
-
我用的是3.1.1版本
1楼 bowengod 2014-12-02 22:01发表 [回复]-
-
你好,我用过MyBatis的二级缓存,是通过实现Cache接口来跟memcached集成,我发现我Mapper中的select语句并没有设置参数 useCache=true,却用上了二级缓存。。。
Re: 干净的句号 2015-04-14 16:05发表 [回复]-
-
回复bowengod:useCache默认是开启的。
public final class MappedStatement {
//other property
private boolean useCache;
Re: 亦山 2014-12-03 08:59发表 [回复]-
-
回复bowengod:这可能跟MyBatis的版本有关吧,我介绍的MyBatis源码是3.2.7版本的,你用的什么版本?
Re: wuzhicheng123456 2015-06-16 17:27发表 [回复]-
-
回复亦山:我的是3.1.1默认为true