Mybatis源码学习(七)mybatis缓存详解
前言
MyBatis 包含一个非常强大的查询缓存特性,使用缓存可以使应用更快地获取数据,避免频繁的数据库交互.让我们开始学习一下mybatis的缓存吧.
1. 一级缓存
- 一级缓存默认会启用,想要关闭一级缓存可以在select标签上配置flushCache=“true”
- 一级缓存存在于 SqlSession 的生命周期中,在同一个 SqlSession 中查询时, MyBatis 会把执行的方法和参数通过算法生成缓存的键值,将键值和查询结果存入一个 Map对象中。如果同一个 SqlSession 中执行的方法和参数完全一致,那么通过算法会生成相同的键值,当 Map 缓存对象中己经存在该键值时,则会返回缓存中的对象
- 任何的 INSERT 、UPDATE 、 DELETE 操作都会清空一级缓存
1.1 一级缓存对象的初始化
BaseExecutor中维护的一级缓存对象.在BaseExecutor维护了两个一级缓存的对象.在构造函数里进行对象的初始化.
/**
* 本地缓存, 一级缓存
*/
protected PerpetualCache localCache;
/**
* 本地输出参数的一级缓存
*/
protected PerpetualCache localOutputParameterCache;
/**受保护的构造方法
* @param configuration 核心配置对象
* @param transaction 事务
*/
protected BaseExecutor(Configuration configuration, Transaction transaction) {
this.transaction = transaction;
this.deferredLoads = new ConcurrentLinkedQueue<>();
this.localCache = new PerpetualCache("LocalCache");
this.localOutputParameterCache = new PerpetualCache("LocalOutputParameterCache");
this.closed = false;
this.configuration = configuration;
this.wrapper = this;
}
1.2 一级缓存对象的使用
查询时先从缓存中查询,没有才进行数据库查询,查询完毕会将查询到的结果放入缓存中.方便后续使用.
1.3 缓存key的生成方式
public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
if (closed) {
throw new ExecutorException("Executor was closed.");
}
//创建一个cacheKey
CacheKey cacheKey = new CacheKey();
//使用mappedStatement的id,作为计算缓存key值的一部分
cacheKey.update(ms.getId());
//使用分页对象的offset,作为计算缓存key值的一部分
cacheKey.update(rowBounds.getOffset());
//使用分页对象的limit,作为计算缓存key值的一部分
cacheKey.update(rowBounds.getLimit());
//使用原始的sql语句,作为计算缓存key值的一部分
cacheKey.update(boundSql.getSql());
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();
// mimic DefaultParameterHandler logic
for (ParameterMapping parameterMapping : parameterMappings) {
//使用sql的输入参数,作为计算缓存key值的一部分
if (parameterMapping.getMode() != ParameterMode.OUT) {
Object value;
String propertyName = parameterMapping.getProperty();
if (boundSql.hasAdditionalParameter(propertyName)) {
value = boundSql.getAdditionalParameter(propertyName);
} else if (parameterObject == null) {
value = null;
} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
value = parameterObject;
} else {
MetaObject metaObject = configuration.newMetaObject(parameterObject);
value = metaObject.getValue(propertyName);
}
cacheKey.update(value);
}
}
//使用环境参数,作为计算缓存key值的一部分
if (configuration.getEnvironment() != null) {
// issue #176
cacheKey.update(configuration.getEnvironment().getId());
}
return cacheKey;
}
2. 二级缓存
- 二级缓存存在于 SqlSessionFactory 的生命周期中,可以理解为跨sqlSession;缓存是以namespace为单位的,不同namespace下的操作互不影响.
- setting参数 cacheEnabled,这个参数是二级缓存的全局开关,默认值是 true,如果把这个参数设置为 false,即使有后面的二级缓存配置,也不会生效
- 要开启二级缓存,你需要在你的 mapper.xml中添加配置
2.1 二级缓存对象的初始化
如果Mapper.xml中配置了二级缓存标签,再构建MappedStatement时会创建一个二级缓存对象
CachingExecutor中维护了一个事务缓存管理器,来管理二级缓存.
public class TransactionalCacheManager {
private final Map<Cache, TransactionalCache> transactionalCaches = new HashMap<>();
public void clear(Cache cache) {
getTransactionalCache(cache).clear();
}
public Object getObject(Cache cache, CacheKey key) {
return getTransactionalCache(cache).getObject(key);
}
public void putObject(Cache cache, CacheKey key, Object value) {
getTransactionalCache(cache).putObject(key, value);
}
public void commit() {
for (TransactionalCache txCache : transactionalCaches.values()) {
txCache.commit();
}
}
public void rollback() {
for (TransactionalCache txCache : transactionalCaches.values()) {
txCache.rollback();
}
}
private TransactionalCache getTransactionalCache(Cache cache) {
return transactionalCaches.computeIfAbsent(cache, TransactionalCache::new);
}
}
2.2 二级缓存对象的使用
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
//从statement中获取缓存
Cache cache = ms.getCache();
if (cache != null) {
//如果statement中配置了<cache>
//根据配置清除缓存
flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) {
//如果使用缓存,并且结果处理器为空
//确保未使用输出参数
ensureNoOutParams(ms, boundSql);
@SuppressWarnings("unchecked")
//获取缓存
List<E> list = (List<E>) tcm.getObject(cache, key);
if (list == null) {
//缓存为空,调用代理类查询
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);
}
2.3 缓存key的生成方式
缓存key的生成方式和一级缓存一样
public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
if (closed) {
throw new ExecutorException("Executor was closed.");
}
//创建一个cacheKey
CacheKey cacheKey = new CacheKey();
//使用mappedStatement的id,作为计算缓存key值的一部分
cacheKey.update(ms.getId());
//使用分页对象的offset,作为计算缓存key值的一部分
cacheKey.update(rowBounds.getOffset());
//使用分页对象的limit,作为计算缓存key值的一部分
cacheKey.update(rowBounds.getLimit());
//使用原始的sql语句,作为计算缓存key值的一部分
cacheKey.update(boundSql.getSql());
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();
// mimic DefaultParameterHandler logic
for (ParameterMapping parameterMapping : parameterMappings) {
//使用sql的输入参数,作为计算缓存key值的一部分
if (parameterMapping.getMode() != ParameterMode.OUT) {
Object value;
String propertyName = parameterMapping.getProperty();
if (boundSql.hasAdditionalParameter(propertyName)) {
value = boundSql.getAdditionalParameter(propertyName);
} else if (parameterObject == null) {
value = null;
} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
value = parameterObject;
} else {
MetaObject metaObject = configuration.newMetaObject(parameterObject);
value = metaObject.getValue(propertyName);
}
cacheKey.update(value);
}
}
//使用环境参数,作为计算缓存key值的一部分
if (configuration.getEnvironment() != null) {
// issue #176
cacheKey.update(configuration.getEnvironment().getId());
}
return cacheKey;
}
mybatis中缓存对比
总结
mybatis每次查询时都会优先从缓存中获取数据先查二级缓存,再查一级缓存二级缓存以namespace为单位的,是SqlSession共享的,容易出现脏读,建议避免使用二级缓存一级缓存是SqlSession独享的,建议开启
喜欢的小伙伴请动动小手关注和点赞吧,也可留言一起探讨怎样更好的学习源码!!!
mybatis带中文注释源码地址
文章链接
Mybatis源码学习(一)初探执行流程
Mybatis源码学习(二)配置文件解析到SqlSessionFactory构建
Mybatis源码学习(三)SqlSession详解
Mybatis源码学习(四)自定义Mapper方法执行流程
Mybatis源码学习(五)Executor和StatementHandler详解
Mybatis源码学习(六)结果集自动封装机制
Mybatis源码学习(七)mybatis缓存详解
Mybatis源码学习(八)Mybatis设计模式总结