相关内容:
架构师系列内容:架构师学习笔记(持续更新)

MyBatis 缓存详解

cache 缓存

缓存是一般的ORM 框架都会提供的功能,目的就是提升查询的效率和减少数据库的压力。跟Hibernate 一样,MyBatis 也有一级缓存和二级缓存,并且预留了集成第三方缓存的接口。
缓存体系结构
MyBatis 跟缓存相关的类都在cache 包里面,其中有一个Cache 接口,只有一个默认的实现类PerpetualCache,它是用HashMap 实现的。
除此之外,还有很多的装饰器,通过这些装饰器可以额外实现很多的功能:回收策略、日志记录、定时刷新等等。

但是无论怎么装饰,经过多少层装饰,最后使用的还是基本的实现类(默认PerpetualCache)。

所有的缓存实现类总体上可分为三类:基本缓存、淘汰算法缓存、装饰器缓存。
缓存实现类

缓存实现类 描述 作用 装饰条件
基本缓存 缓存基本实现类 默认是PerpetualCache,也可以自定义比如RedisCache、EhCache 等,具备基本功能的缓存类
LruCache LRU 策略的缓存 当缓存到达上限时候,删除最近最少使用的缓存(Least Recently Use) eviction="LRU(默认)
FifoCache FIFO 策略的缓存 当缓存到达上限时候,删除最先入队的缓存 eviction=“FIFO”
SoftCache,WeakCache 带清理策略的缓存 通过JVM 的软引用和弱引用来实现缓存,当JVM内存不足时,会自动清理掉这些缓存,基于SoftReference 和WeakReference eviction=“SOF”
LoggingCache 带日志功能的缓存 比如:输出缓存命中率 eviction=“WEAK”
SynchronizedCache 同步缓存 基于 synchronized关键字实现,解决并发问题 基本
BlockingCache 阻塞缓存 通过在 get/put方式中加锁,保证只有一个线程操作缓存,基于 Java重入锁实现 基本
SerializedCache 支持序列化的缓存 将对象序列化以后存到缓存中,取出时反序列化 blocking=true
ScheduledCache 定时调度的缓存 在进行get/put/remove/getSize 等操作前,判断缓存时间是否超过了设置的最长缓存时间(默认是一小时),如果是则清空缓存–即每隔一段时间清空一次缓存 readOnly=false(默认)
TransactionalCache 事务缓存 在二级缓存中使用,可一次存入多个缓存,移除多个缓存 flushInterval 不为空

一级缓存

一级缓存(本地缓存)介绍
一级缓存也叫本地缓存,MyBatis 的一级缓存是在会话(SqlSession)层面进行缓存的。MyBatis 的一级缓存是默认开启的,不需要任何的配置。
首先我们必须去弄清楚一个问题,在MyBatis 执行的流程里面,涉及到这么多的对象,那么缓存PerpetualCache 应该放在哪个对象里面去维护?如果要在同一个会话里面共享一级缓存,这个对象肯定是在SqlSession 里面创建的,作为SqlSession 的一个属性。
DefaultSqlSession 里面只有两个属性,Configuration 是全局的,所以缓存只可能放在Executor 里面维护——SimpleExecutor/ReuseExecutor/BatchExecutor 的父类BaseExecutor 的构造函数中持有了PerpetualCache。
在同一个会话里面,多次执行相同的SQL 语句,会直接从内存取到缓存的结果,不会再发送SQL 到数据库。但是不同的会话里面,即使执行的SQL 一模一样(通过一个Mapper 的同一个方法的相同参数调用),也不能使用到一级缓存。

一级缓存验证
判断是否命中缓存:如果再次发送SQL 到数据库执行,说明没有命中缓存;如果直接打印对象,说明是从内存缓存中取到了结果。
1、在同一个session 中共享

BlogMapper mapper = session.getMapper(BlogMapper.class);
System.out.println(mapper.selectBlog(1));
System.out.println(mapper.selectBlog(1));

2、不同session 不能共享

SqlSession session1 = sqlSessionFactory.openSession();
BlogMapper mapper1 = session1.getMapper(BlogMapper.class);
System.out.println(mapper.selectBlog(1));

PS:一级缓存在BaseExecutor 的query()——queryFromDatabase()中存入。在queryFromDatabase()之前会get()。
3、同一个会话中,update(包括delete)会导致一级缓存被清空

mapper.updateByPrimaryKey(blog);
session.commit();
System.out.println(mapper.selectBlogById(1));

一级缓存是在BaseExecutor 中的update()方法中调用clearLocalCache()清空的(无条件),query 中会判断。
4、其他会话更新了数据,导致读取到脏数据(一级缓存不能跨会话共享)

// 会话2 更新了数据,会话2 的一级缓存更新
BlogMapper mapper2 = session2.getMapper(BlogMapper.class);
mapper2.updateByPrimaryKey(blog);
session2.commit();
// 会话1 读取到脏数据,因为一级缓存不能跨会话共享
System.out.println(mapper1.selectBlog(1));

一级缓存的不足
使用一级缓存的时候,因为缓存不能跨会话共享,不同的会话之间对于相同的数据可能有不一样的缓存。在有多个会话或者分布式环境下,会存在脏数据的问题。如果要解决这个问题,就要用到二级缓存。

二级缓存

二级缓存介绍
二级缓存是用来解决一级缓存不能跨会话共享的问题的,范围是namespace 级别的,可以被多个SqlSession 共享(只要是同一个接口里面的相同方法,都可以共享),生命周期和应用同步。
思考一个问题:如果开启了二级缓存,二级缓存应该是工作在一级缓存之前,还是在一级缓存之后呢?二级缓存是在哪里维护的呢?答:作为一个作用范围更广的缓存,它肯定是在SqlSession 的外层,否则不可能被多个SqlSession 共享。而一级缓存是在SqlSession 内部的,所以第一个问题,肯定是工作在一级缓存之前,也就是只有取不到二级缓存的情况下才到一个会话中去取一级缓存。
第二个问题,二级缓存放在哪个对象中维护呢? 要跨会话共享的话,SqlSession 本身和它里面的BaseExecutor 已经满足不了需求了,那我们应该在BaseExecutor 之外创建一个对象。
实际上MyBatis 用了一个装饰器的类来维护,就是CachingExecutor。如果启用了二级缓存,MyBatis 在创建Executor 对象的时候会对Executor 进行装饰。
CachingExecutor 对于查询请求,会判断二级缓存是否有缓存结果,如果有就直接返回,如果没有委派交给真正的查询器Executor 实现类,比如SimpleExecutor 来执行查询,再走到一级缓存的流程。最后会把结果缓存起来,并且返回给用户。

开启二级缓存的方法
第一步:在mybatis-config.xml 中配置了(可以不配置,默认是true):

<setting name="cacheEnabled" value="true"/>

只要没有显式地设置cacheEnabled=false,都会用CachingExecutor 装饰基本的执行器。
第二步:在Mapper.xml 中配置标签:

<!-- 声明这个namespace 使用二级缓存-->
<cache type="org.apache.ibatis.cache.impl.PerpetualCache"size="1024" <!—最多缓存对象个数,默认1024-->eviction="LRU" <!—回收策略-->flushInterval="120000" <!—自动刷新时间ms,未配置时只有调用时刷新-->readOnly="false"/> <!—默认是false(安全),改为true 可读写时,对象必须支持序列
化-->

cache 属性详解:

属性 含义 取值
type 缓存实现类 需要实现Cache 接口,默认是PerpetualCache
size 最多缓存对象个数 默认1024
eviction 回收策略(缓存淘汰算法) LRU – 最近最少使用的:移除最长时间不被使用的对象(默认)。FIFO – 先进先出:按对象进入缓存的顺序来移除它们。SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象。WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。
flushInterval 定时自动清空缓存间隔 自动刷新时间,单位ms,未配置时只有调用时刷新
readOnly 是否只读 true:只读缓存;会给所有调用者返回缓存对象的相同实例。因此这些对象不能被修改。这提供了很重要的性能优势。false:读写缓存;会返回缓存对象的拷贝(通过序列化),不会共享。这会慢一些,但是安全,因此默认是false。改为false 可读写时,对象必须支持序列化。
blocking 是否使用可重入锁实现缓存的并发控制 true,会使用BlockingCache 对Cache 进行装饰,默认false

Mapper.xml 配置了<cache>之后,select()会被缓存。update()、delete()、insert()会刷新缓存。
思考:如果cacheEnabled=true,Mapper.xml 没有配置标签,还有二级缓存吗?还会出现CachingExecutor 包装对象吗?
只要cacheEnabled=true 基本执行器就会被装饰。有没有配置<cache>,决定了在启动的时候会不会创建这个mapper 的Cache 对象,最终会影响到CachingExecutor.query 方法里面的判断:

if (cache != null)

如果某些查询方法对数据的实时性要求很高,不需要二级缓存,怎么办?我们可以在单个Statement ID 上显式关闭二级缓存(默认是true):

<select id="selectBlog" resultMap="BaseResultMap" useCache="false">

二级缓存验证

1、事务不提交,二级缓存不存在

BlogMapper mapper1 = session1.getMapper(BlogMapper.class);
System.out.println(mapper1.selectBlogById(1));
// 事务不提交的情况下,二级缓存不会写入
// session1.commit();
BlogMapper mapper2 = session2.getMapper(BlogMapper.class);
System.out.println(mapper2.selectBlogById(1));

思考:为什么事务不提交,二级缓存不生效?
因为二级缓存使用TransactionalCacheManager(TCM)来管理,最后又调用了TransactionalCache 的getObject()、putObject 和commit()方法,TransactionalCache里面又持有了真正的Cache 对象,比如是经过层层装饰的PerpetualCache。
在putObject 的时候,只是添加到了entriesToAddOnCommit 里面,只有它的commit()方法被调用的时候才会调用flushPendingEntries()真正写入缓存。它就是在DefaultSqlSession 调用commit()的时候被调用的。

2、使用不同的session 和mapper,验证二级缓存可以跨session 存在
取消以上commit()的注释

3、在其他的session 中执行增删改操作,验证缓存会被刷新

Blog blog = new Blog();
blog.setBid(1);
blog.setName("357");
mapper3.updateByPrimaryKey(blog);
session3.commit();
// 执行了更新操作,二级缓存失效,再次发送SQL 查询
System.out.println(mapper2.selectBlogById(1));

思考:为什么增删改操作会清空缓存?
在CachingExecutor 的update()方法里面会调用flushCacheIfRequired(ms),isFlushCacheRequired 就是从标签里面渠道的flushCache 的值。而增删改操作的flushCache 属性默认为true。

什么时候开启二级缓存?

一级缓存默认是打开的,二级缓存需要配置才可以开启。那么我们必须思考一个问题,在什么情况下才有必要去开启二级缓存?
1、因为所有的增删改都会刷新二级缓存,导致二级缓存失效,所以适合在查询为主的应用中使用,比如历史交易、历史订单的查询。否则缓存就失去了意义。
2、如果多个namespace 中有针对于同一个表的操作,比如Blog 表,如果在一个namespace 中刷新了缓存,另一个namespace中没有刷新,就会出现读到脏数据的情况。所以,推荐在一个Mapper 里面只操作单表的情况使用。
思考:如果要让多个namespace 共享一个二级缓存,应该怎么做?
跨namespace 的缓存共享的问题,可以使用<cache-ref>来解决:

<cache-ref namespace="com.gupaoedu.crud.dao.DepartmentMapper" />

cache-ref 代表引用别的命名空间的Cache 配置,两个命名空间的操作使用的是同一个Cache。在关联的表比较少,或者按照业务可以对表进行分组的时候可以使用。
注意:在这种情况下,多个Mapper 的操作都会引起缓存刷新,缓存的意义已经不大了。

第三方缓存做二级缓存

除了MyBatis 自带的二级缓存之外,我们也可以通过实现Cache 接口来自定义二级缓存。
MyBatis 官方提供了一些第三方缓存集成方式,比如ehcache 和redis:https://github.com/mybatis/redis-cache
pom 文件引入依赖:

<dependency><groupId>org.mybatis.caches</groupId><artifactId>mybatis-redis</artifactId><version>1.0.0-beta2</version>
</dependency>

Mapper.xml 配置,type 使用RedisCache:

<cache type="org.mybatis.caches.redis.RedisCache"eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>

redis.properties 配置:

host=localhost
port=6379
connectionTimeout=5000
soTimeout=5000
database=0

MyBatis 一级缓存二级缓存详解相关推荐

  1. Mybatis3.4.x技术内幕(二十二):Mybatis一级、二级缓存原理分析

    2019独角兽企业重金招聘Python工程师标准>>> Mybatis的一级缓存,指的是SqlSession级别的缓存,默认开启:Mybatis的二级缓存,指的是SqlSession ...

  2. mybatis一级,二级缓存。缓存带来的脏读问题

    title 1. 关于缓存的介绍 2. 一级缓存,默认开启,session级别 3. 二级缓存,mapper 的namespace级别 1. 关于缓存的介绍 Mybatis一级缓存的作用域是同一个Sq ...

  3. 一级缓存--二级缓存详解

    一级缓存 由于一级缓存的作用域是SqlSession内部,但是SqlSession的生命周期非常短暂,所以一级缓存对于查询效率的提升很有限.而要提升效率,需要使用二级缓存. 二级缓存 1.开启全局开关 ...

  4. 深入浅出 MyBatis 的一级、二级缓存机制

    一.MyBatis 缓存 缓存就是内存中的数据,常常来自对数据库查询结果的保存.使用缓存,我们可以避免频繁与数据库进行交互,从而提高响应速度. MyBatis 也提供了对缓存的支持,分为一级缓存和二级 ...

  5. mybatis教程--查询缓存(一级缓存二级缓存和整合ehcache)

    查询缓存 1 缓存的意义 将用户经常查询的数据放在缓存(内存)中,用户去查询数据就不用从磁盘上(关系型数据库数据文件)查询,从缓存中查询,从而提高查询效率,解决了高并发系统的性能问题. 2 mybat ...

  6. 【mybatis】Mybatis中的一级、二级缓存

    [mybatis]简介 [mybatis]mybatis & mybatis-plus & hibernate的区别 [mybatis]核心成员分析 [mybatis]Mybatis的 ...

  7. 【MyBatis学习13】MyBatis中的二级缓存

    1. 二级缓存的原理 前面介绍了,mybatis中的二级缓存是mapper级别的缓存,值得注意的是,不同的mapper都有一个二级缓存,也就是说,不同的mapper之间的二级缓存是互不影响的.为了更加 ...

  8. 并发-MESI缓存一直协议详解

    并发-MESI缓存一直协议详解 CPU缓存一致性协议MESI CPU高速缓存(Cache Memory) CPU为何要有高速缓存 目前流行的多级缓存结构 多核CPU多级缓存一致性协议MESI MESI ...

  9. MyBatis中的二级缓存

    MyBatis中的二级缓存 1. 二级缓存的原理 前面介绍了,mybatis中的二级缓存是mapper级别的缓存,值得注意的是,不同的mapper都有一个二级缓存,也就是说,不同的mapper之间的二 ...

最新文章

  1. django 使用json.dumps转换queryset的datatime报错问题解决
  2. 1035. 插入与归并(25)
  3. IIS基础:返回404错误页面的合理设置方式
  4. Django-form表单
  5. Linux系统网络基础知识及配置
  6. hiveserver2和metastore简要概括作用
  7. 有感于最近一个朋友买股票大亏
  8. 《史无前例!编程语言python斩获最有发展第一与排行榜第三!》深入 Python 流程控制
  9. 与敏捷团队一起交付价值
  10. [CF809E] Surprise me!
  11. 源码分析--SDWebImage
  12. 机器学习 --- 4. 大内密探HMM(隐马尔可夫)围捕赌场老千(转)
  13. 在 java 中_关于final 关键字,在Java中,关于final关键字的说法正确的是()
  14. Nodejs操作Access数据库
  15. python三维数据欠采样_数据分析:使用Imblearn处理不平衡数据(过采样、欠采样)...
  16. SQL server 2000个人版 下载地址
  17. 解决COVID-19的7个开放硬件项目
  18. MYSQL操作数据库-------查看、删除数据库
  19. 交通一卡通二维码支付技术要求
  20. android商户扫码枪读取手机二维码

热门文章

  1. Java学习系列(十一)Java面向对象之I/O流(下)
  2. ngx-material中Datepicker的日期格式化和选择语系
  3. HTML中的padding和margin
  4. 我的地盘我做主—玩转Python函数和变量
  5. CocoaPods 添加第三方库报错
  6. 前台之boostrap
  7. 使用python标准库urllib2访问网页
  8. 第三方支付处理厂商软件有漏洞,日本美容零售商Acro 10万支付卡信息遭攻击
  9. 谷歌再修复已遭利用的两个高危 Chrome 0day
  10. Drupal 更新开源编辑器 CKEditor,修复两个 XSS 漏洞