这是我参与11月更文挑战的第18天,活动详情查看:2021最后一次更文挑战
Mybatis一级缓存
前提
Mybatis的一级缓存一般SqlSession级别的,是默认开启的
在将源码解析之前,需要带着以下几个疑问进行阅读
- 一级缓存到底是什么?
- 一级缓存什么时候被创建?
- 一级缓存的工作流程是什么?
一级缓存是什么?
打开SqlSession发现,目前跟只有clearCache()
方法跟缓存有关系 点开clearCache()
按照以下顺序依次点开
再深⼊分析,流程⾛到Perpetualcache
中的clear()
⽅法之后,会调⽤其cache.clear()
⽅法,那么这个cache是什么东⻄呢?点进去发现,cache其实就是private Map<Object, Object> cache = new HashMap<>();
HashMap();也就是⼀个Map,所以说cache.clear()其实就是map.clear(),也就是说,缓存其实就是本地存放的⼀个map对象,每⼀个SqISession都会存放⼀个map对象的引⽤。那么第一个疑问就已经能够解答,一级缓存本质上是一个HashMap对象。
一级缓存什么时候被创建的?
一般来讲,是在Executor中创建的,因为Executor是执行器,用来执行sql请求,⽽且清除缓存的⽅法也在Executor中执⾏,所以很可能缓存的创建也很有可能在Executor中。我们可以看看Executor方法:
Executor中有⼀个createCacheKey()
⽅法,这个⽅法是创建缓存的⽅法啊,跟进去看看,你发现createCacheKey()
⽅法是由BaseExecutor执⾏的,createCacgeKey()
代码如下:
CacheKey cacheKey = new CacheKey();
/MappedStatement的id
// id就是Sql语句的所在位置包名+类名+ SQL名称
cacheKey.update(ms.getId());
// offset就是0
cacheKey.update(rowBounds.getOffset());
// limit就是Integer.MAXVALUE
cacheKey.update(rowBounds.getLimit());
//具体的SQL语句
cacheKey.update(boundSql.getSql());
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();
// mimic DefaultParameterHandler logic
for (ParameterMapping parameterMapping : parameterMappings) {
if (parameterMapping.getMode() != ParameterMode.OUT) {
Object value;
...
//后⾯是update了sql中带的参数
cacheKey.update(value);
}
}
if (configuration.getEnvironment() != null) {
// issue #176
cacheKey.update(configuration.getEnvironment().getId());
}
return cacheKey;
复制代码
创建缓存key会经过⼀系列的update⽅法,udate⽅法由⼀个CacheKey这个对象来执⾏的,这个update⽅法最终由updateList的list来把五个值存进去,对照上⾯的代码和下⾯的图示,你应该能理解这五个值都是什么了
这⾥需要注意⼀下最后⼀个值,configuration.getEnvironment().getId()这是什么,这其实就是定义在mybatis-config.xml中的标签,⻅如下。
所以一级缓存是在执行器的子类BaseExecutor的createCacgeKey()方法中创建的。
一级缓存的工作流程?
一级缓存工作流程图
通过BaseExecutor的query()方法,代码如下
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
/拿到转换后的sql
BoundSql boundSql = ms.getBoundSql(parameter);
//创建缓存
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
//调用具体实现方法
return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
复制代码
@SuppressWarnings("unchecked")
Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler,CacheKey key,BoundSql boundSql) throws SQLException {
...
//判断缓存的map集合中是否存有插入的sql数据,如果有就直接去除,如果没有就查询数据库
list=resultHandler==null? (List<E>) localCache.getObject(key) : null;
if (list!=null) {
//这个主要是处理存储过程⽤的。
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
list=queryFromDatabase(ms, parameter, rowBounds, resultHandler, key,boundSql);
}
...
}
复制代码
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
List<E> list;
localCache.putObject(key, EXECUTION_PLACEHOLDER);
try {
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
localCache.removeObject(key);
}
//把数据库查询结果存到一级缓存中
localCache.putObject(key, list);
if (ms.getStatementType() == StatementType.CALLABLE) {
localOutputParameterCache.putObject(key, parameter);
}
return list;
}
复制代码
通过上述代码,客户端调用query()
方法,query()
方法进过一系列的转换后拿到转换后的boundSql,并且创建Cache缓存,然后一起传入下一个query()
方法中,query()方法通过一个三元表示式表示如果查不到的话,就从数据库查,在queryFromDatabase()
方法中,会对localcache进⾏写⼊。localcache对象的put⽅法最终交给Map进⾏存放。这个就是基本一级缓存工作流程
private Map<Object, Object> cache = new HashMap<Object, Object>();
@Override
public void putObject(Object key, Object value) {
cache.put(key, value);
}
复制代码