MyBatis 缓存 - 下:二级缓存
通过上篇文章我们已经知道,MyBatis 一级缓存的最大共享范围为 SqlSession,即一次会话中,而且有可能会因为缓存没更新而导致脏读问题。如果需要在多个 SqlSession 中共享缓存,那么就需要开启二级缓存。
SqlSession 的创建会借助 SqlSessionFactory,而 DefaultSqlSession
可由 DefaultSqlSessionFactory#openSession
获得,Executor 通过 Configuration#newExecutor
方法获得,该方法会检查 cacheEnabled
配置,开启就意味着启用二级缓存:
![](https://img.haomeiwen.com/i7460499/ad59ad0e3bfc4130.png)
配置
MyBatis 配置文件通过如下配置开启:
<setting name="cacheEnabled" value="true"/>
一个 mapper 文件有唯一的 namespace (<mapper namespace="***">
),在 mapper 文件中可通过 <cache/>
标签声明该 namespace 使用二级缓存。<cache-ref/>
可配置引用其他的命名空间,那么当前命名空间将与引用的命名空间使用同一个缓存(对于同一命名空间下的多表查询可借助该标签避免脏读问题)。<cache/>
标签有如下可选属性:
- type:cache 类型,默认是 PerpetualCache
- eviction: 回收的策略,比如 FIFO,LRU。
- flushInterval: 配置一定时间自动刷新缓存,单位是毫秒。
- size: 最多缓存对象的个数。
- readOnly: 是否只读,若配置可读写,则需要对应的实体类能够序列化。
- blocking: 若缓存中找不到对应的key,是否一直阻塞,直到有对应的数据进入缓存。
装饰器模式以及 TransactionalCacheManager
CachingExecutor 使用了装饰器模式,delegate 即为被装饰的 Executor 对象,tcm 为 TransactionalCacheManager
类型,tcm 管理了以 namespace 为单位的缓存。
![](https://img.haomeiwen.com/i7460499/1383fb9e18fa7160.png)
先看
putObject
方法,内部调用了 getTransactionalCache
方法,该方法从 transactionalCaches
中取到了具体的 TransactionalCache
,这里以 namespace 为单位的缓存作为 key,将这个 Cache 进行再包装的 TransactionalCache
作为值,TransactionalCache
处理了事务相关的一些特性。
缓存更新和获取
缓存的获取与一级缓存无异,都封装在 Executor#query
中:
![](https://img.haomeiwen.com/i7460499/d462a89bd041d939.png)
区别是二级缓存通过
TransactionalCacheManager
来操作缓存。
在默认的设置中 SELECT(Executor#query
) 语句不会刷新缓存,insert/update/delte (Executor#update
)会刷新缓存。
private void flushCacheIfRequired(MappedStatement ms) {
Cache cache = ms.getCache();
if (cache != null && ms.isFlushCacheRequired()) {
tcm.clear(cache);
}
}
调用 SqlSession#commit
二级缓存才会生效
![](https://img.haomeiwen.com/i7460499/edac1fefb2773d1e.png)
已经知道在 Executor#query
方法中如果缓存没命中,那么从数据库中查询后会将数据新增到缓存中,但实际上此时还没有将数据放入缓存( HashMap
),那 tcm.putObject
语句把数据放到了哪里呢 ?
tcm.putObject
会根据 Cache (作为 key)从 TransactionalCacheManager#transactionalCaches
中得到包装后的 TransactionalCache
,再调用其 TransactionalCache#putObject
方法:
![](https://img.haomeiwen.com/i7460499/4500f8c27bcc27a2.png)
通过变量名就能知道
entriesToAddOnCommit
在调用 commit
时才会真正放入缓存的 HashMap 中,事实也是如此:![](https://img.haomeiwen.com/i7460499/970aeb71e9c92d83.png)
entriesMissedInCache
用于统计命中率。
参考文章:聊聊 MyBatis 缓存机制