缓存
如下图所示,在开启了二级缓存的情况下:在执行 select 查询的时候,MyBatis 会先从二级缓存中取输入,其次才是一级缓存,即 MyBatis 查询数据的顺序是:二级缓存 ———> 一级缓存 ——> 数据库。在数据库查询到数据是会第一时间保存到一级缓存,然后会调用tcm.putObject(cache, key, list)方法暂存到暂存TransactionalCache中,等到SqlSession commit 再保存到二级缓存中。
缓存的key
- Mapper的Id,即Mapper命名空间与<select|update|insert|delete>标签的Id组成的全局限定名。
- 查询结果的偏移量及查询的条数。
- 具体的SQL语句及SQL语句中需要传递的所有参数。
- MyBatis主配置文件中,通过标签配置的环境信息对应的Id属性值。
综上所述,CacheKey 由以下条件决定:statementId + rowBounds + 传递给 JDBC 的 SQL + 传递给 JDBC 的参数值;
一级缓存
BaseExecutor 有一个PerpetualCache属性
如果查不到的话,就从数据库查,在queryFromDatabase中,会对localcache进⾏写⼊。 localcache 对象的put⽅法最终交给Map进⾏存放
如果中间sqlSession去执⾏commit操作(执⾏插⼊、更新、删除),则会清空SqlSession中的 ⼀ 级缓存,这样做的⽬的为了让缓存中存储的是最新的信息,避免脏读。
private Map<Object, Object> cache = new HashMap<Object, Object>();
@Override
public void putObject(Object key, Object value) { cache.put(key, value);
}
一级缓存的生命周期
-
MyBatis 在开启一个数据库会话时,会创建一个新的 SqlSession 对象,SqlSession 对象中会有一个新的 Executor 对象,Executor 对象中持有一个新的 PerpetualCache 对象;当会话结束时,SqlSession 对象及其内部的 Executor 对象还有 PerpetualCache 对象也一并释放掉。
-
如果 SqlSession 调用了 close() 方法,会释放掉一级缓存 PerpetualCache 对象,一级缓存将不可用;
-
如果 SqlSession 调用了 clearCache() ,会清空 PerpetualCache 对象中的数据,但是该对象仍可使用;
-
SqlSession 中执行了任何一个 update 操作 (update()、delete()、insert()),都会清空 PerpetualCache 对象的数据,但是该对象可以继续使用;
二级缓存
如下代码可以看出CachingExecutor 是 Executor 的装饰者,以增强 Executor 的功能,使其具有缓存查询的功能,这里用到了设计模式中的装饰者模式。
private TransactionalCache getTransactionalCache(Cache cache) {
return transactionalCaches.computeIfAbsent(cache, TransactionalCache::new);
}
对于每个 Cache 而言,都有一个容量限制,MyBatis 提供了各种策略来对 Cache 缓存的容量进行控制,以及对 Cache 中的数据进行刷新和置换。MyBatis 主要提供了以下几个刷新和置换策略:
LRU:(Least Recently Used), 最近最少使用算法,即如果缓存中容量已经满了,会将缓存中最近最少被使用的缓存记录清除掉,然后添加新的记录;
FIFO:(First in first out), 先进先出算法,如果缓存中的容量已经满了,那么会将最先进入缓存中的数据清除掉;
Scheduled:指定时间间隔清空算法,该算法会以指定的某一个时间间隔将 Cache 缓存中的数据清空;
二级缓存查询流程如下:
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
// 从 MappedStatement 中获取 Cache,注意这里的 Cache 是从MappedStatement中获取的
// 也就是我们上面解析Mapper中<cache/>标签中创建的,它保存在Configration中
// 我们在初始化解析xml时分析过每一个MappedStatement都有一个Cache对象,就是这里
Cache cache = ms.getCache();
// 如果配置文件中没有配置 <cache>,则 cache 为空
if (cache != null) {
//如果需要刷新缓存的话就刷新:flushCache="true"
flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) {
// 暂时忽略,存储过程相关
ensureNoOutParams(ms, boundSql);
@SuppressWarnings("unchecked")
// 从二级缓存中,获取结果
List<E> list = (List<E>) tcm.getObject(cache, key);
if (list == null) {
// 如果没有值,则执行查询,这个查询实际也是先走一级缓存查询,一级缓存也没有的话,则进行DB查询
list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
// 缓存查询结果
tcm.putObject(cache, key, list); // issue #578 and #116
}
// 如果存在,则直接返回结果
return list;
}
}
// 不使用缓存,则从数据库中查询(会查一级缓存)
return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}