记录是一种精神,是加深理解最好的方式之一。

最近看了下Mybatis的源码,分析了二级缓存的实现方式,在这里把他记下来。虽然这不复杂,对这方面的博客也有很多,写的也很好。但我坚信看懂了是其一,能够教别人或者描述清楚记下来才能真正的掌握。
曹金桂 cao_jingui@163.com (如有欠缺还请指教)
时间:2016年10月11日16:00

这篇文章能够帮你

  • 学会对Mybatis配置二级缓存
  • 学会Mybatis二级缓存的实现方式
  • 学会整合外部缓存框架(如:Ehcache)
  • 学会自定义二级缓存

1. Mybatis内部二级缓存的配置

要使用Mybatis的二级缓存,需要对Mybatis进行配置,配置分三步

  • Mybatis全局配置中启用二级缓存配置
<setting name="cacheEnabled" value="true"/>
  • 在对应的Mapper.xml中配置cache节点
<mapper namespace="userMapper"><cache /><result ... /><select ... />
</mapper>
  • 在对应的select查询节点中添加useCache=true
<select id="findUserById" parameterType="int" resultMap="user" useCache="true">select * from users where id=#{id};
</select>
  • 高级配置

a. 为每一个Mapper分配一个Cache缓存对象(使用<cache>节点配置)
b. 多个Mapper共用一个Cache缓存对象(使用<cache-ref>节点配置)

只要简单的三步配置即可开启Mybatis的二级缓存了。在使用mybatis查询时候("userMapper.findUserById"),不同会话(Sqlsession)在查询时候,只会查询数据库一次,第二次会从二级缓存中读取。

@Before
public void before() {String mybatisConfigFile = "MybatisConfig/Mybatis-conf.xml";InputStream stream = TestMybatis.class.getClassLoader().getResourceAsStream(mybatisConfigFile);sqlSessionFactory = new SqlSessionFactoryBuilder().build(stream); //构建sqlSession的工厂
}
@Test
public void test() {SqlSession sqlSession = sqlSessionFactory.openSession();User i = sqlSession.selectOne("userMapper.findUserById", 1);System.out.println(i);sqlSession.close();sqlSession = sqlSessionFactory.openSession();User x = sqlSession.selectOne("userMapper.findUserById", 1); // 读取二级缓存数据System.out.println(x);sqlSession.close();
}

2. Mybatis内部二级缓存的设计及工作模式

首先我们要知道,mybatis的二级缓存是通过CacheExecutor实现的。CacheExecutor其实是Executor的代理对象。所有的查询操作,在CacheExecutor中都会先匹配缓存中是否存在,不存在则查询数据库。

3. 内部二级缓存的实现详解

竟然知道Mybatis二级缓存是通过CacheExecotur实现的,那看下Mybatis中创建Executor的过程

// 创建执行器(Configuration.newExecutor)
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {//确保ExecutorType不为空(defaultExecutorType有可能为空)executorType = executorType == null ? defaultExecutorType : executorType;executorType = executorType == null ? ExecutorType.SIMPLE : executorType;Executor executor;if (ExecutorType.BATCH == executorType) {executor = new BatchExecutor(this, transaction);} else if (ExecutorType.REUSE == executorType) {executor = new ReuseExecutor(this, transaction);} else {executor = new SimpleExecutor(this, transaction);}if (cacheEnabled) { //重点在这里,如果启用全局代理对象,返回Executor的Cache包装类对象executor = new CachingExecutor(executor);}executor = (Executor) interceptorChain.pluginAll(executor);return executor;
}

重点在cacheEnabled这个参数。如果你看了我的文章[Mybatis配置文件解析过程详解],就应该知道了怎么设置cacheEnabled。对,就是此文章第一点说的开启Mybatis的全局配置项。我们继续看下CachingExecutor具体怎么实现的。

public class CachingExecutor implements Executor {private Executor delegate;public CachingExecutor(Executor delegate) {this.delegate = delegate;delegate.setExecutorWrapper(this);}public int update(MappedStatement ms, Object parameterObject) throws SQLException {flushCacheIfRequired(ms); //是否需要更缓存return delegate.update(ms, parameterObject);  //更新数据}......
}

很清晰,静态代理模式。在CachingExecutor的所有操作都是通过调用内部的delegate对象执行的。缓存只应用于查询,我们看下CachingExecutor的query方法。

public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {BoundSql boundSql = ms.getBoundSql(parameterObject);//创建缓存值CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);//获取记录return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)throws SQLException {Cache cache = ms.getCache();if (cache != null) {flushCacheIfRequired(ms);if (ms.isUseCache() && resultHandler == null) {// ensureNoOutParamsif (ms.getStatementType() == StatementType.CALLABLE) {for (ParameterMapping parameterMapping : boundSql.getParameterMappings()) {if (parameterMapping.getMode() != ParameterMode.IN) {throw new ExecutorException("Caching stored procedures with OUT params is not supported.  Please configure useCache=false in " + ms.getId() + " statement.");}}}List<E> list = (List<E>) tcm.getObject(cache, key); //从缓存中获取数据if (list == null) {list = delegate.<E>query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);tcm.putObject(cache, key, list); // 结果保存到缓存中}return list;}}return delegate.<E>query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}

如果MappedStatement中对应的Cache存在,并且对于的查询开启了二级缓存(useCache="true"),那么在CachingExecutor中会先从缓存中根据CacheKey获取数据,如果缓存中不存在则从数据库获取。这里的代码很简单,很容易理解。
说到缓存,有效期和缓存策略不得不提。在Mybatis中二级缓存也实现了有效期的控制和缓存策略。Mybatis中是使用装饰模式实现的,具体可以看下mybatis的cache包

具体于配置如下:

<cache eviction="FIFO|LRU|SOFT|WEAK" flushInterval="300" size="100" />

对应具体实现源码可以参考CacheBuilder类的源码。

public Cache build() {if (implementation == null) { //缓存实现类implementation = PerpetualCache.class;if (decorators.size() == 0) {decorators.add(LruCache.class);}}Cache cache = newBaseCacheInstance(implementation, id);setCacheProperties(cache);if (PerpetualCache.class.equals(cache.getClass())) {for (Class<? extends Cache> decorator : decorators) {cache = newCacheDecoratorInstance(decorator, cache);setCacheProperties(cache);}// 采用默认缓存包装类cache = setStandardDecorators(cache);} else if (!LoggingCache.class.isAssignableFrom(cache.getClass())) {cache = new LoggingCache(cache);}return cache;
}

4. 一级缓存和二级缓存的使用顺序

如果你的MyBatis使用了二级缓存,并且你的Mapper和select语句也配置使用了二级缓存,那么在执行select查询的时候,MyBatis会先从二级缓存中取输入,其次才是一级缓存,即MyBatis查询数据的顺序是:

二级缓存 ———> 一级缓存——> 数据库

5. mybatis二级缓存和分页插件同时使用产生的问题

问题:分页插件开启二级缓存后,分页查询时无论查询哪一页都返回第一页的数据

在之前讲解Mybatis的执行流程的时候提到,在开启cache的前提下,Mybatis的executor会先从缓存里读取数据,读取不到才去数据库查询。问题就出在这里,sql自动生成插件和分页插件执行的时机是在statementhandler里,而statementhandler是在executor之后执行的,无论sql自动生成插件和分页插件都是通过改写sql来实现的,executor在生成读取cache的key(key由sql以及对应的参数值构成)时使用都是原始的sql,这样当然就出问题了。
找到问题的原因后,解决起来就方便了。只要通过拦截器改写executor里生成key的方法,在生成可以时使用自动生成的sql(对应sql自动生成插件)或加入分页信息(对应分页插件)就可以了。
参考:http://blog.csdn.net/hupanfeng/article/details/16950161

6. mybatis整合第三方缓存框架

我们以ehcache为例。对于ehcache我只会简单的使用。这里我只是介绍Mybatis怎么使用ehcache,不对ehcache配置作说明。我们知道,在配置二级缓存时候,我们可以指定对应的实现类。这里需要mybatis-ehcache-1.0.3.jar这个jar包。在Mapper中我们只要配置如下即可。

<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>

当然,项目中ehcache的配置还是需要的。

小结

对于Mybatis整合第三方的缓存,实现骑士很简单,只要在配置的地方制定实现类即可。
Mybatis默认二级缓存的实现在集群或者分布式部署下是有问题的,Mybatis默认缓存只在当节点内有效,并且对缓存的失效操作无法同步的其他节点。需要整合第三方分布式缓存实现,如ehcache或者自定义实现。

作者:曹金桂
链接:https://www.jianshu.com/p/5ff874fa696f
來源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

Mybatis二级缓存原理相关推荐

  1. 【MyBatis框架】查询缓存-二级缓存原理

    二级缓存原理 1.原理 首先看图 首先开启mybatis的二级缓存. sqlSession1去查询用户id为1的用户信息,查询到用户信息会将查询数据存储到二级缓存中. 如果SqlSession3去执行 ...

  2. mybatis 二级缓存失效_给我五分钟,带你彻底掌握MyBatis的缓存工作原理

    前言 在计算机的世界中,缓存无处不在,操作系统有操作系统的缓存,数据库也会有数据库的缓存,各种中间件如Redis也是用来充当缓存的作用,编程语言中又可以利用内存来作为缓存.自然的,作为一款优秀的ORM ...

  3. SpringBoot整合Redis配置MyBatis二级缓存

    目录 写在前面 源码获取 一.MyBatis缓存机制 1.1.一级缓存 1.2.二级缓存 二.集成Redis 2.1.安装Redis 2.2.项目引入Redis 2.2.1.Maven依赖 2.2.2 ...

  4. 深入了解MyBatis二级缓存

    深入了解MyBatis二级缓存 一.创建Cache的完整过程 我们从SqlSessionFactoryBuilder解析mybatis-config.xml配置文件开始: Reader reader ...

  5. MyBatis复习(六):MyBatis二级缓存

    Mybatis缓存分为一级缓存和二级缓存 MyBatis一级缓存是默认开启的,数据存储范围是SqlSession会话这个级别,当SqlSession关闭后,缓存就会被清除,生命周期非常短. MyBat ...

  6. Mybatis一级缓存、整合第三方缓存ehcache、Mybatis二级缓存

    8. 缓存 8.1 一级缓存 在对数据库的一次会话中,我们有可能会反复地执行完全相同的查询语句,如果不采取一些措施的话,每一次查询都会查询一次数据库,而我们在极短的时间内做了完全相同的查询,那么它们的 ...

  7. Mybatis 二级缓存简单示例

    简介 简单接收Mybatis中二级缓存的使用示例 概览 主要部分如下: 引入Maven依赖 SpringBoot配置文件配置.建库与初始化SQL语句 实体类与Mapper编写 测试 Maven依赖 完 ...

  8. Mybatis二级缓存的缺陷

    一级缓存默认是开启的(但是整合了Spring,Mybatis的一级缓存默认就失效了) 二级缓存是要手动配置开启的(二级缓存是mapper级别的缓存,可以跨SqlSession) Mybatis二级缓存 ...

  9. mybatis二级缓存

    mybatis二级缓存 Mybatis中一级缓存 和 二级缓存的区别 一级缓存(本地缓存) 二级缓存(全局缓存) 二级缓存开启 mybatis解读 总结 Mybatis中一级缓存 和 二级缓存的区别 ...

最新文章

  1. python打包工具哪个好用_python打包工具比较
  2. 第六十九期:【学习分享】温水里的程序员,技术将成为温水
  3. python 无法引入同级目录的方法_再见 virtualenv!K神教你轻松管理多个Python环境...
  4. 在linux下运行qiime,科学网—QIIME2在Linux环境下使用遇到的问题 - 肖斌的博文
  5. C#中的方法(函数),委托和事件
  6. 博客园文章中图片太大显示不全的解决办法
  7. 【软件体系结构】软件体系结构风格
  8. VUE-waterfall瀑布流组件使用
  9. KingbaseES 中的xmin,xmax等系统字段说明
  10. 净亏损大幅减少超90%,阿里影业“回归内容”的背后逻辑是什么?
  11. 史上绝地反击,美式英语英文学习大全。美国英语最新词频表
  12. 用yolov5做人脸检测
  13. 艾宾浩斯遗忘规律学习记东西(诗词,单词等)
  14. Socket通信接口对接
  15. php warning require,Thinkphp 网站打开提示Warning: require(): open_basedir restriction in effect另一种解决方法...
  16. Wi-Fi Mesh网络技术
  17. 数据中心如何向私有云转变
  18. html页面清除缓存
  19. 让iOS开发变得更有效率-分类、工具类
  20. 《世界因你而不同-李开复自传》读书笔记(3/3)

热门文章

  1. Dubbo入门实例--转载
  2. quartz 报错:java.lang.classNotFoundException
  3. 【Python】写文件个性化设置模块Python_Xlwt练习
  4. 解析金融反欺诈技术的应用与实践
  5. C语言中嵌入正则表达式
  6. 基于大数据与深度学习的自然语言对话
  7. 聚焦互联网应用和深度学习
  8. 关于yum安装后提示There are no enabled repos Run yum repolist all..的问题解决
  9. oracle删除表中数据_工作表中提供的特定数据在数据库中批量删除的方法
  10. DDD - 聚合与聚合根_如何理解 Respository与DAO