一文彻底读懂优秀开源产品MyBatis一级缓存设计!
孙玄
奈学教育CEO
读完需要
3
分钟
速读仅需 1 分钟
孙玄, 现任奈学教育科技创始人&CEO ,毕业于浙大,前百度资深研发工程师、前 58 集团技术委员会主席/高级系统架构师到前转转公司技术委员会主席/首席架构师/大中后台技术负责人。江湖人称“玄姐”,出版过《百万年薪架构师修炼之路》书籍。
1
前言
缓存是 MyBatis 中非常重要的特性。合理使用缓存能够减少数据库 IO,显著提升系统性能。但是在分布式环境下,如果使用不当,则可能会带来数据一致性问题。MyBatis 提供了一级缓存和二级缓存,其中一级缓存基于 SqlSession 实现,而二级缓存基于 Mapper,本文将会详细讲解一级缓存。
2
CACHE 缓存
MyBatis 跟缓存相关的类都在 Cache 包里面,其中有一个 Cache 接口,只有一个默认的实现类 PerpetualCache,它是用 HashMap 实现的。除此之外,还有很多的装饰器,通过这些装饰器可以额外实现很多的功能:回收策略、日志记录、定时刷新等等。
2.1
包结构
如图是缓存类所在源码中所处的位置,从包名中我们可以知道 decorators 包中存放的是一些装饰类。
2.2
查看装饰后的结果
但是无论怎么装饰,经过多少层装饰,最后使用的还是基本的实现类(默认 PerpetualCache)。我们 debug 看一下经层层装饰后的结果如图:
CachingExecutor中Debug查看:缓存的层层装饰
我们后面说的一级缓存缓存就是存放到这个 PerpetualCache 里面。
2.3
对装饰器的分类
3
一级缓存的特点
3.1
一级缓存默认是开启的,而且不能关闭
至于一级缓存为什么不能关闭,MyBatis 核心开发人员做出了解释:
MyBatis 的一些关键特性(例如通过和建立级联映射、避免循环引用(circular references)、加速重复嵌套查询等)都是基于 MyBatis 一级缓存实现的,而且 MyBatis 结果集映射相关代码重度依赖 CacheKey,所以目前 MyBatis 不支持关闭一级缓存。
虽然我们不能关闭一级缓存,但是我们可以更改他的作用范围:
MyBatis 提供了一个配置参数 localCacheScope,用于控制一级缓存的级别,该参数的取值为 SESSION、STATEMENT,当指定 localCacheScope 参数值为 SESSION 时,缓存对整个 SqlSession 有效,只有执行 DML 语句(更新语句)时,缓存才会被清除。当 localCacheScope 值为 STATEMENT 时,缓存仅对当前执行的语句有效,当语句执行完毕后,缓存就会被清空。
<settings><setting name="localCacheScope" value="STATEMENT"/>
</settings>
能更改一级缓存的作用范围这一点很重要后面我们讲解中会用到这一特性。
3.2
一级缓存默认是 SqlSession 级别的
在操作数据库时需要构造 sqlSession 对象,在对象中有一个(内存区域)数据结构(HashMap)用于存储缓存数据。不同的 sqlSession 之间的缓存数据区域(HashMap)是互相不影响的。用一张图来表示一下一级缓存,其中每一个 SqlSession 的内部都会有一个一级缓存对象。
4
实验验证一级缓存的作用范围
4.1
一级缓存同一个会话共享数据
模拟思路:打开一个会话,进行两次查询通过日志查看第二次是否走数据库。
代码如下:
@Testpublic void testSession() throws IOException {try (SqlSession sqlSession = sqlSessionFactory.openSession()) {OrderMapper orderMapper = sqlSession.getMapper(OrderMapper.class);List<Order> orders = orderMapper.queryById(620898339119480832L);System.out.println("第一次查询:" + JSON.toJSONString(orders));OrderMapper orderMapper2 = sqlSession.getMapper(OrderMapper.class);List<Order> orders2 = orderMapper2.queryById(620898339119480832L);System.out.println("第二次查询:" + JSON.toJSONString(orders2));}}
日志信息如下:
分析:第一次查询打印了 sql 日志信息,说明是通过数据库获取到数据,第二次也查询到数据但是没有打印日志信息,说明走了缓存。
结论:一级缓存同一个会话共享数据。
4.2
同一个会话如果有更新操作则缓存清除
代码如下:
@Test
public void testUpdate() throws IOException {try (SqlSession sqlSession = sqlSessionFactory.openSession()) {// 同一个会话 第一次查询System.out.println("第一次会会话的 第一次查询");OrderMapper orderMapper = sqlSession.getMapper(OrderMapper.class);List<Order> orders = orderMapper.queryById(620898339119480832L);Order order = orders.get(0);order.setAmount(12L);// 进行更新orderMapper.updateByPrimaryKey(order);// 同一个会话 第二次查询System.out.println("第一次会会话的 第二次查询");List<Order> orders2 = orderMapper.queryById(620898339119480832L);System.out.println(JSON.toJSONString(orders2));}
}
日志信息如下:
分析:第一次查询打印了 sql 日志,然后进行数据更新,最后进行第二次查询发现仍旧查询数据库,说明缓存已经失效。
结论:同一个会话如果有更新操作则缓存清除。
4.3
导致脏数据
模拟思路:打开两个会话,第一个会话查询数据库获取数据后 ,接着第二个会话修改数据,最后第一个会话再查询数据,那最后这次查询如果和第一次查询相同,那说明一级缓存会导致脏数据问题。
代码如下:
@Testpublic void testDirtyData() throws IOException {SqlSession sqlSession1 = sqlSessionFactory.openSession(true);SqlSession sqlSession2 = sqlSessionFactory.openSession(true);try {// 同一个会话 第一次查询OrderMapper orderMapper1 = sqlSession1.getMapper(OrderMapper.class);OrderMapper orderMapper2 = sqlSession2.getMapper(OrderMapper.class);List<Order> orders = orderMapper1.queryById(620898339119480832L);System.out.println("第一次会会话第一次查询的结果" + orders);Order order1 = getOrder(orders);System.out.println("=========更新数据======");orderMapper2.updateByPrimaryKey(order1);sqlSession2.commit();List<Order> orders1 = orderMapper1.queryById(620898339119480832L);System.out.println("第二次查询:"+JSON.toJSONString(orders1));}catch (Exception e){sqlSession1.close();sqlSession2.close();}}
日志信息如下:
分析:第一个会话第一次查询 amount 值是1212,第二会话将 amount 更改为666,第一个会话再次查询数据获取的 amount 值为1212,这说明第一个会话的第二次查询命中缓存导致了脏数据问题。
结论:一级缓存在多会话中会导致脏数据。
解决方式:在配置一级缓存作用范围的时候将其设置为 STATEMENT,那么缓存仅对当前执行的语句有效,当语句执行完毕后,缓存就会被清空。
设置方式:
<settings><setting name="localCacheScope" value="STATEMENT"/>
</settings>
- EOF -
想要加入中生代架构群的小伙伴,请添加群合伙人大白的微信
申请备注(姓名+公司+技术方向)才能通过哦!
工程师成长系列推荐
多隆:从工程师到阿里巴巴合伙人
2020-11-10
为什么说IT科技公司应该留住35岁员工?
2020-11-05
工程师的基本功是什么?如何练习?听美团技术大咖怎么说
2020-10-19
美团技术专家云鹏:写给工程师的十条精进原则!
2020-10-15
找CTO杜仲:再谈中年危机和应对策略
2020-10-10
Erik Dietrich:二十年的编程,教会我的五件事!
2020-09-22
Mobvista首席架构师蔡超:工作感悟之失败与成功,我的8点总结
2020-09-20
奈学教育CEO孙玄:成为一个有情怀的工程师,我的12点思考
2020-09-19
Netstars CTO陈斌:架构师的成长之路
2020-09-17
左耳朵耗子:程序员如何把控自己的职业?
2020-08-22
RocketMQ 大神丁威亲述参与开源社区的方式
2020-11-17
END
#架构师必备#点分享点点赞点在看
一文彻底读懂优秀开源产品MyBatis一级缓存设计!相关推荐
- 一文读懂 Copyleft 开源许可证
开源组件已改变了我们开发软件的方式.来自开源社区的现成库(ready-made libraries)使忙碌的开发者们能专注于他们的秘密武器,这些秘密武器或将成为未来令人兴奋的新软件产品.而且不需要付费 ...
- 一文读懂电商产品架构
目录 一文读懂电商产品架构 电商平台架构的组成部分 前端展示 后台管理 数据存储
- 一文彻底读懂物联网关键技术之——ZigBee!
一文彻底读懂物联网关键技术之--ZigBee! 本文采用问答形式向你详细地介绍了方方面面,不夸口的说,你所需要知道的关于 ZigBee的一切,在这里基本可以了解到! 在智能硬件和物联网领域,时下大名鼎 ...
- 一文深入浅出读懂NoSQL
一文深入浅出读懂NoSQL 2016-11-25 Runoot.com ICT架构师技术交流 NoSQL(NoSQL = Not Only SQL ),意即"不仅仅是SQL".在现 ...
- 一文读懂关于开源的7大理念
原文:https://www.enmotech.com/web/detail/1/844/1.html 关于开源的7大理念 (上) https://www.enmotech.com/web/detai ...
- 10分钟读懂什么是产品定位
解释下定位这个概念,<韦氏词典>里对定位的解释是:针对敌人(竞争对手)确立最具优势的位置.这个定义其实和战略非常相近. 这个概念也是近十几年才被提出的,有人认为定位的出现是促进人类生产力再 ...
- 一文极速读懂 Gene Ontology (GO)数据库
一.介绍 官方:基因本体(GO)知识库是有关基因功能的全球最大信息来源. 这些知识既是人类可读的,也是机器可读的,并且是生物医学研究中大规模分子生物学和遗传学实验的计算分析的基础. 在读懂基因本体论( ...
- 开源组件是什么意思_一文读懂常用开源许可证
社区时常为流行产品中有争议的开源许可证而感到震惊,这引起各方关注,纷纷争论何为真正的开源许可证.去年,Apache 基金会(Apache Foundation)禁止使用 Facebook React ...
- 一文读懂常用开源许可证
社区时常为流行产品中有争议的开源许可证而感到震惊,这引起各方关注,纷纷争论何为真正的开源许可证.去年,Apache 基金会(Apache Foundation)禁止使用 Facebook React ...
最新文章
- PyTorch: 序列到序列模型(Seq2Seq)实现机器翻译实战
- java字节对齐 32 64_【C语言】字节对齐问题(以32位系统为例)
- 新时代的网络工程师需要掌握哪些技能
- Educational Codeforces Round 2 B. Queries about less or equal elements
- 如何优化 Java 性能?
- 每天一道LeetCode-----找到给定数组中第三大的值
- 一个智能运维算法测试方法
- 计算机开机时间停在上次关机,怎么在电脑开机的时候查看上次关机前的操作
- 修复IE下列表 li 的阶梯Bug
- tomcat搭建之Jenkins环境
- linux下安装ansys
- 实例:用C#.NET手把手教你做微信公众号开发(1)--接入
- heartbeat 高可用工具
- mysql substr 中文乱码_substr()乱码
- 移动应用广告之商业变现的实现策略
- Java习题练习:杨辉三角
- 长期坐着不动会得什么病?
- 浏览记录-history
- position有几种属性?
- 【RAII】RAII 技术(内存安全解决技术/自动化解锁技术)
热门文章
- matlab gui实例_App Designer 自学实例8
- java 获取继承字段_java – 从类中获取所有字段(甚至是私有的和继承的)
- python做图像识别该学什么_Python实现图片识别加翻译【高薪必学】
- 计算机网络之网络层:12、网络层设备
- PyQt5学习笔记06----Qt Designer自定义信号emit及传参
- 利用PowerShell Empire实现Word文档DDE攻击控制(简单没啥用)
- JS DOM事件(常用消息、常用事件、addEventListener、removeEventListener)
- char[] 和char*之间的相互转换
- 操作系统实践(四/五)
- 生活中要常常鼓励别人