MyBatis二级缓存原理详解

MyBatis的二级缓存是Application级别的缓存,它可以提高对数据库查询的效率,以提高应用的性能。本文将全面分析MyBatis的二级缓存的设计原理。

1.MyBatis的缓存机制整体设计以及二级缓存的工作模式

如上图所示,当开一个会话时,一个SqlSession对象会使用一个Executor对象来完成会话操作,MyBatis的二级缓存机制的关键就是对这个Executor对象做文章。如果用户配置了"cacheEnabled=true",那么MyBatis在为SqlSession对象创建Executor对象时,会对Executor对象加上一个装饰者:CachingExecutor,这时SqlSession使用CachingExecutor对象来完成操作请求。CachingExecutor对于查询请求,会先判断该查询请求在Application级别的二级缓存中是否有缓存结果,如果有查询结果,则直接返回缓存结果;如果缓存中没有,再交给真正的Executor对象来完成查询操作,之后CachingExecutor会将真正Executor返回的查询结果放置到缓存中,然后在返回给用户。

CachingExecutorExecutor的装饰者,以增强Executor的功能,使其具有缓存查询的功能,这里用到了设计模式中的装饰者模式,

CachingExecutorExecutor的接口的关系如下类图所示:

2 . MyBatis二级缓存的划分

MyBatis并不是简单地对整个Application就只有一个Cache缓存对象,它将缓存划分的更细,即是Mapper级别的,即每一个Mapper都可以拥有一个Cache对象具体如下:

b.多个Mapper共用一个Cache缓存对象(使用节点配置)

如果你想让多个Mapper公用一个Cache的话,你可以使用节点,来指定你的这个Mapper使用到了哪一个MapperCache缓存

3.使用二级缓存,必须要具备的条件

MyBatis对二级缓存的支持粒度很细,它会指定某一条查询语句是否使用二级缓存。

虽然在Mapper中配置了,并且为此Mapper分配了Cache对象,这并不表示我们使用Mapper中定义的查询语句查到的结果都会放置到Cache对象之中,我们必须指定Mapper中的某条选择语句是否支持缓存,即如下所示,在 节点中配置useCache="true",Mapper才会对此Select的查询支持缓存特性,否则,不会对此Select查询,不会经过Cache缓存。如下所示,Select语句配置了useCache="true",则表明这条Select语句的查询会使用二级缓存。

总之,要想使某条Select查询支持二级缓存,你需要保证:

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

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

5.二级缓存实现的选择

1.MyBatis自身提供的二级缓存的实现

MyBatis自身提供了丰富的,并且功能强大的二级缓存的实现,它拥有一系列的Cache接口装饰者,可以满足各种对缓存操作和更新的策略。

MyBatis定义了大量的Cache的装饰器来增强Cache缓存的功能,如下类图所示。

对于每个Cache而言,都有一个容量限制,MyBatis各供了各种策略来对Cache缓存的容量进行控制,以及对Cache中的数据进行刷新和置换。下面来看不同置换策略实现类的源码:

MyBatis源码分析——Cache接口以及实现Cache接口

MyBatis中的Cache以SPI实现,给需要集成其它Cache或者自定义Cache提供了接口。

img

Cache实现

Cache的实现类中,Cache有不同的功能,每个功能独立,互不影响,则对于不同的Cache功能,这里使用了装饰者模式实现。

PerpetualCache

作为为最基础的缓存类,底层实现比较简单,直接使用了HashMap。

FifoCache

FIFO回收策略,装饰类,内部维护了一个队列,来保证FIFO,一旦超出指定的大小,则从队列中获取Key并从被包装的Cache中移除该键值对。

LoggingCache

日志功能,装饰类,用于记录缓存的命中率,如果开启了DEBUG模式,则会输出命中率日志。

LruCache

LRU回收策略,装饰类,在内部保存一个LinkedHashMap,用以实现LRU。

ScheduledCache

定时清空Cache,但是并没有开始一个定时任务,而是在使用Cache的时候,才去检查时间是否到了。

SynchronizedCache

同步Cache,实现比较简单,直接使用synchronized修饰方法。

SoftCache

软引用回收策略,软引用只有当内存不足时才会被垃圾收集器回收。这里的实现机制中,使用了一个链表来保证一定数量的值即使内存不足也不会被回收,但是没有保存在该链表的值则有可能会被回收。

在WeakHashMap中,可以看到是将引用应用到Key的,当Key被回收后,则移除相关的Value。但是这里是将其应用到Value中,因为Key不能被回收,如果被移除的话,就会影响到整个体系,最底层的实现使用HashMap实现的,没有Key,就没有办法移除相关的值。反过来,值被回收了,将软引用对象放到队列中,可以根据Key调用removeObject移除该关联的键和软引用对象。

WeakCache

弱引用回收策略,弱引用的对象一旦被垃圾收集器发现,则会被回收,无论内存是否足够。这里的实现和上面的软引用类似,除了使用WeakReference替换掉SoftReference,其它基本一样。还有一点想不通的就是,为什么SoftCache加锁了,而这里没有加锁。

TransactionalCache

事务缓存,在提交的时候,才真正的放到Cache中,或者回滚的时候清除掉,对Cache没有影响。

SerializedCache

序列化功能,将值序列化后存到缓存中。该功能用于缓存返回一份实例的Copy,用于保存线程安全。

2、 用户自定义的Cache接口实现

在Mapper文件里配置使用该自定义的缓存对象,如:

测试如下:

日志输出如下:

可以看出,每次查询数据库前,MyBatis都会先在缓存中查找是否有该缓存对象。只有当调用了commit() 方法,MyBatis才会往缓存中写入数据,数据记录的键为 格式,值为返回的对象值。

3.跟第三方内存缓存库的集成(Ehcache)

MyBatis与Ehcache整合所需工具:https://gitee.com/sixiaojie/codes/hgumrf4k8ew2v1cjyqn7o57

1.加入ehcache包

ehcache-core-2.6.5.jar和mybatis-ehcache-1.0.2.jar

一个是ehcache自己的,一个是和mybatis的整合包

2.整合ehcache

配置mapper中cache中的type为ehcache对cache接口的实现类型。

我们在mybatis-ehcache-1.0.2.jar下找到org.mybatis.caches.ehcache包下有EhcacheCache.class类,这个就是ehcache整合mybatis的Cache接口的实现

UserMapper.xml:

3.加入ehcache的配置文件

在classpath下配置ehcache.xml

代码如下:

4.ehcache.xml属性配置详解

1、diskStore :指定数据(.data and .index)存储位置,可指定磁盘中的文件夹位置

2、defaultCache : 默认的管理策略

一、以下属性是必须的:

1、name: Cache的名称,必须是唯一的(ehcache会把这个cache放到HashMap里)。

2、maxElementsInMemory:在内存中缓存的element的最大数目。

3、maxElementsOnDisk:在磁盘上缓存的element的最大数目,默认值为0,表示不限制。

4、eternal:设定缓存的elements是否永远不过期。如果为true,则缓存的数据始终有效,如果为false那么还要根据timeToIdleSeconds,timeToLiveSeconds判断。

5、overflowToDisk: 如果内存中数据超过内存限制,是否要缓存到磁盘上。

二、以下属性是可选的:

1、timeToIdleSeconds: 对象空闲时间,指对象在多长时间没有被访问就会失效。只对eternal为false的有效。默认值0,表示一直可以访问。

2、timeToLiveSeconds: 对象存活时间,指对象从创建到失效所需要的时间。只对eternal为false的有效。默认值0,表示一直可以访问。

3、diskPersistent: 是否在磁盘上持久化。指重启jvm后,数据是否有效。默认为false。

4、diskExpiryThreadIntervalSeconds: 对象检测线程运行时间间隔。标识对象状态的线程多长时间运行一次。

5、diskSpoolBufferSizeMB: DiskStore使用的磁盘大小,默认值30MB。每个cache使用各自的DiskStore。

6、memoryStoreEvictionPolicy: 如果内存中数据超过内存限制,向磁盘缓存时的策略。默认值LRU,可选FIFO、LFU。

三、缓存的3 种清空策略 :

1、FIFO ,first in first out (先进先出).

2、LFU , Less Frequently Used (最少使用).意思是一直以来最少被使用的。缓存的元素有一个hit 属性,hit 值最小的将会被清出缓存。

3、LRU ,Least Recently Used(最近最少使用,即未被使用时间最长). (ehcache 默认值).缓存的元素有一个时间戳,当缓存容量满了,而又需要腾出地方来缓存新的元素的时候,那么现有缓存元素中时间戳离当前时间最远的元素将被清出缓存。

觉得本文不错,顺手点个赞哦~~您的鼓励,是我继续分享知识的强大动力!

如果您觉得有不妥或者错误的地方,还请您不吝指教!

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20181125G0VPXH00?refer=cp_1026
  • 腾讯「云+社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。

扫码关注云+社区

领取腾讯云代金券