type
status
date
slug
summary
tags
category
icon
password
MyBatis作为一个强大的持久层框架,缓存是其必不可少的功能之一,Mybatis中的缓存分为一级缓存和二级缓存。但本质上是一样的,都是使用Cache接口实现的。缓存位于 org.apache.ibatis.cache包下。

Cache其实使用到了装饰器模式
来实现缓存的处理。首先大家需要先回顾下装饰器模式的相关内容哦。我们先来看看Cache中的基础类的API

1. Cache接口
Cache接口的实现类很多,但是大部分都是装饰器,只有PerpetualCache提供了Cache接口的基本实现。

2. PerpetualCache
PerpetualCache在缓存模块中扮演了ConcreteComponent的角色,其实现比较简单,底层使用HashMap记录缓存项,具体的实现如下:
我们可以来看看cache.decorators包下提供的装饰器。他们都实现了Cache接口。这些装饰器都在PerpetualCache的基础上提供了一些额外的功能,通过多个组合实现一些特殊的需求。
3. BlockingCache
是一个阻塞同步的缓存,它保证只有一个线程到缓存中查找指定的key对应的数据。
通过源码我们能够发现,BlockingCache本质上就是在我们操作缓存数据的前后通过 ReentrantLock对象来实现了加锁和解锁操作。其他的具体实现类,大家可以自行查阅
缓存实现类 | 描述 | 作用 | 装饰条件 |
基本缓存 | 缓存基本实现类 | 默认是PerpetualCache,也可以自定义比如RedisCache、EhCache等,具备基本功能的缓存类 | 无 |
LruCache | LRU策略的缓存 | 当缓存到达上限时候,删除最近最少使用的缓存(Least Recently Use) | eviction="LRU"(默认) |
FifoCache | FIFO策略的缓存 | 当缓存到达上限时候,删除最先入队的缓存 | eviction="FIFO" |
SoftCacheWeakCache | 带清理策略的缓存 | 通过JVM的软引用和弱引用来实现缓存,当JVM内存不足时,会自动清理掉这些缓存,基于SoftReference和WeakReference | eviction="SOFT"eviction="WEAK" |
LoggingCache | 带日志功能的缓存 | 比如:输出缓存命中率 | 基本 |
SynchronizedCache | 同步缓存 | 基于synchronized关键字实现,解决并发问题 | 基本 |
BlockingCache | 阻塞缓存 | 通过在get/put方式中加锁,保证只有一个线程操作缓存,基于Java重入锁实现 | blocking=true |
SerializedCache | 支持序列化的缓存 | 将对象序列化以后存到缓存中,取出时反序列化 | readOnly=false(默认) |
ScheduledCache | 定时调度的缓存 | 在进行get/put/remove/getSize等操作前,判断缓存时间是否超过了设置的最长缓存时间(默认是一小时),如果是则清空缓存--即每隔一段时间清空一次缓存 | flushInterval不为空 |
TransactionalCache | 事务缓存 | 在二级缓存中使用,可一次存入多个缓存,移除多个缓存 | 在TransactionalCacheManager中用Map维护对应关系 |
4. 缓存的应用
4.1. 缓存对应的初始化
在Configuration初始化的时候会为我们的各种Cache实现注册对应的别名
在解析settings标签的时候,设置的默认值有如下
cacheEnabled默认为true,localCacheScope默认为 SESSION
在解析映射文件的时候会解析我们相关的cache标签
然后解析映射文件的cache标签后会在Configuration对象中添加对应的数据在
可以发现 如果存储 cache 标签,那么对应的 Cache对象会被保存在 currentCache 属性中。
进而在 Cache 对象 保存在了 MapperStatement 对象的 cache 属性中。
openSession的时候又做了哪些操作,在创建对应的执行器的时候会有缓存的操作?
也就是如果 cacheEnabled 为 true 就会通过 CachingExecutor 来装饰executor 对象,然后就是在执行SQL操作的时候会涉及到缓存的具体使用。这个就分为一级缓存和二级缓存,这个我们来分别介绍
4.2. 一级缓存
一级缓存也叫本地缓存(Local Cache),MyBatis的一级缓存是在会话(SqlSession)层面进行缓存的。MyBatis的一级缓存是默认开启的,不需要任何的配置(如果要关闭,localCacheScope设置为STATEMENT)。在BaseExecutor对象的query方法中有关闭一级缓存的逻辑
```然后我们需要考虑下在一级缓存中的 PerpetualCache 对象在哪创建的,因为一级缓存是Session级别的缓存,肯定需要在Session范围内创建,其实PerpetualCache的实例化是在BaseExecutor的构造方法中创建的

一级缓存的具体实现也是在BaseExecutor的query方法中来实现的

通过输出我们能够发现,不同的Session中的相同操作,一级缓存是没有起作用的。
4.3. 二级缓存
二级缓存是用来解决一级缓存不能跨会话共享的问题的,范围是namespace级别的,可以被多个SqlSession共享(只要是同一个接口里面的相同方法,都可以共享),生命周期和应用同步。
二级缓存的设置,首先是settings中的cacheEnabled要设置为true,当然默认的就是为true,这个步骤决定了在创建Executor对象的时候是否通过CachingExecutor来装饰。
那么设置了cacheEnabled标签为true是否就意味着 二级缓存是否一定可用呢?当然不是,我们还需要在 对应的映射文件中添加 cache 标签才行。
```cache属性详解:
属性 | 含义 | 取值 |
type | 缓存实现类 | 需要实现Cache接口,默认是PerpetualCache,可以使用第三方缓存 |
size | 最多缓存对象个数 | 默认1024 |
eviction | 回收策略(缓存淘汰算法) | LRU – 最近最少使用的:移除最长时间不被使用的对象(默认)。FIFO – 先进先出:按对象进入缓存的顺序来移除它们。SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象。WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。 |
flushInterval | 定时自动清空缓存间隔 | 自动刷新时间,单位 ms,未配置时只有调用时刷新 |
readOnly | 是否只读 | true:只读缓存;会给所有调用者返回缓存对象的相同实例。因此这些对象不能被修改。这提供了很重要的性能优势。false:读写缓存;会返回缓存对象的拷贝(通过序列化),不会共享。这会慢一些,但是安全,因此默认是 false。改为false可读写时,对象必须支持序列化。 |
blocking | 启用阻塞缓存 | 通过在get/put方式中加锁,保证只有一个线程操作缓存,基于Java重入锁实现 |
cache标签在源码中的体现,创建cacheKey
createCacheKey

MappedStatement.java 属性 private Cache cache;
而这看到的和我们前面在缓存初始化时看到的 cache 标签解析操作是对应上的。所以我们要开启二级缓存两个条件都要满足。

这样的设置表示当前的映射文件中的相关查询操作都会触发二级缓存,但如果某些个别方法我们不希望走二级缓存怎么办呢?我们可以在标签中添加一个 useCache=false 来实现的设置不使用二级缓存

还有就是当我们执行的对应的DML操作,在MyBatis中会清空对应的二级缓存和一级缓存。
···在解析映射文件的时候DML操作flushCacheRequired为true
4.4. 第三方缓存
在实际开发的时候我们一般也很少使用MyBatis自带的二级缓存,这时我们会使用第三方的缓存工具Ehcache获取Redis来实现,那么他们是如何来实现的呢?
添加依赖
然后加上Cache标签的配置
然后添加redis的属性文件
- Author:atsuc
- URL:https://blog.atsuc.cn/article/blog-source-006
- Copyright:All articles in this blog, except for special statements, adopt BY-NC-SA agreement. Please indicate the source!