一文带你分析Mybatis一级缓存源码

这是我参与11月更文挑战的第18天,活动详情查看:2021最后一次更文挑战

Mybatis一级缓存

前提

Mybatis的一级缓存一般SqlSession级别的,是默认开启的

在将源码解析之前,需要带着以下几个疑问进行阅读

  • 一级缓存到底是什么?
  • 一级缓存什么时候被创建?
  • 一级缓存的工作流程是什么?

一级缓存是什么?

image.png

打开SqlSession发现,目前跟只有clearCache()方法跟缓存有关系 点开clearCache()按照以下顺序依次点开

image.png

再深⼊分析,流程⾛到Perpetualcache中的clear()⽅法之后,会调⽤其cache.clear()⽅法,那么这个cache是什么东⻄呢?点进去发现,cache其实就是private Map<Object, Object> cache = new HashMap<>();

image.png

HashMap();也就是⼀个Map,所以说cache.clear()其实就是map.clear(),也就是说,缓存其实就是本地存放的⼀个map对象,每⼀个SqISession都会存放⼀个map对象的引⽤。那么第一个疑问就已经能够解答,一级缓存本质上是一个HashMap对象。

一级缓存什么时候被创建的?

一般来讲,是在Executor中创建的,因为Executor是执行器,用来执行sql请求,⽽且清除缓存的⽅法也在Executor中执⾏,所以很可能缓存的创建也很有可能在Executor中。我们可以看看Executor方法:

image.png

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来把五个值存进去,对照上⾯的代码和下⾯的图示,你应该能理解这五个值都是什么了

image.png

这⾥需要注意⼀下最后⼀个值,configuration.getEnvironment().getId()这是什么,这其实就是定义在mybatis-config.xml中的标签,⻅如下。

image.png

所以一级缓存是在执行器的子类BaseExecutor的createCacgeKey()方法中创建的。

一级缓存的工作流程?

一级缓存工作流程图

image.png

通过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);
    }
复制代码

おすすめ

転載: juejin.im/post/7032461966606237726