上篇文章提到查问时会用到缓存,其内置的两级缓存如下:

// 一级缓存,在executor中,与sqlsession绑定// org.apache.ibatis.executor.BaseExecutor#localCache// 指向org.apache.ibatis.cache.impl.PerpetualCache#cacheprivate Map<Object, Object> cache = new HashMap<>();// 二级缓存,在MappedStatement中(对应mapper.xml中的一个crud办法),周期与SqlSessionFactory统一org.apache.ibatis.mapping.MappedStatement#cache// 最终也指向了org.apache.ibatis.cache.impl.PerpetualCache#cacheprivate Map<Object, Object> cache = new HashMap<>();
  • 一、二级缓存都是查问缓存,select写入,insert、update、delete则革除
  • 一、二级缓存均指向org.apache.ibatis.cache.impl.PerpetualCache#cache,实质是一个HashMap
  • 一、二级缓存Key的计算形式统一,均指向org.apache.ibatis.executor.BaseExecutor#createCacheKey,Key的实质:statement的id + offset + limit + sql + param参数
  • 一级缓存生命周期和SqlSession统一,默认开启;二级缓存申明周期和SqlSessionFactory统一,需手动开启
  • 雷同namespace应用同一个二级缓存;二级缓存和事务关联,事务提交数据才会写入缓存,事务回滚则不会写入

接下来通过源码别离来看一下。

一级缓存

一级缓存的生命周期是sqlSession;在同一sqlSession中,用雷同sql和查问条件屡次查问DB状况,非首次查问会命中一级缓存。

一级缓存默认是开启的,如果想敞开须要减少配置

// == 如果不设置,默认是SESSION(后续的源码剖析会波及这里)<setting name="localCacheScope" value="STATEMENT"/>

以查询方法作为入口

org.apache.ibatis.session.defaults.DefaultSqlSession#selectList(java.lang.String, java.lang.Object, org.apache.ibatis.session.RowBounds)org.apache.ibatis.executor.BaseExecutor#query(org.apache.ibatis.mapping.MappedStatement, java.lang.Object, org.apache.ibatis.session.RowBounds, org.apache.ibatis.session.ResultHandler)List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {    BoundSql boundSql = ms.getBoundSql(parameter);    // == 计算CacheKey    CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);    // == 查问中应用缓存    return query(ms, parameter, rowBounds, resultHandler, key, boundSql);}

CacheKey计算

org.apache.ibatis.executor.BaseExecutor#createCacheKeyCacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {    CacheKey cacheKey = new CacheKey();    // == 调用update办法批改cache    cacheKey.update(ms.getId());    cacheKey.update(rowBounds.getOffset());    cacheKey.update(rowBounds.getLimit());    cacheKey.update(boundSql.getSql());    // value是参数    cacheKey.update(value);    return cacheKey;}

从这里就能够猜测到,CacheKey和statement的id、offset、limit、sql、param参数无关

进入CacheKey验证这个猜想:

### CacheKey类 ###// 默认37private final int multiplier;// 默认17private int hashcode;private long checksum;private int count;private List<Object> updateList;public void update(Object object) {    int baseHashCode = object == null ? 1 : ArrayUtil.hashCode(object);        // -- 批改几个属性值    count++;    checksum += baseHashCode;    baseHashCode *= count;    hashcode = multiplier * hashcode + baseHashCode;    // -- updateList新增对象    updateList.add(object);}public boolean equals(Object object) {    // -- 比拟几个属性值    if (hashcode != cacheKey.hashcode) {      return false;    }    if (checksum != cacheKey.checksum) {      return false;    }    if (count != cacheKey.count) {      return false;    }    // -- 挨个比拟updateList中的对象    for (int i = 0; i < updateList.size(); i++) {      Object thisObject = updateList.get(i);      Object thatObject = cacheKey.updateList.get(i);      if (!ArrayUtil.equals(thisObject, thatObject)) {        return false;      }    }    return true;}

查问中应用缓存

org.apache.ibatis.executor.BaseExecutor#query(org.apache.ibatis.mapping.MappedStatement, java.lang.Object, org.apache.ibatis.session.RowBounds, org.apache.ibatis.session.ResultHandler, org.apache.ibatis.cache.CacheKey, org.apache.ibatis.mapping.BoundSql)List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {    List<E> list;    try {      queryStack++;      // == 1.先从localCache获取数据      list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;      if (list != null) {          handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);      }       // == 2.缓存中无数据,从数据库查问      else {          list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);      }    }    // ## 如果scope设置成STATEMENT类型,会清理一级缓存    if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {      // 清理缓存      clearLocalCache();    }    return list;}

持续察看代码2地位:

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 {    // == 查问DB逻辑    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;}

综上,查问过程会向localCache中寄存查问后果。
只不过设置scope为STATEMENT时,每次都会清空缓存——这就是一级缓存生效的机密

增删改清理缓存

insert和delete办法都会执行update:

public int insert(String statement) {    return insert(statement, null);}public int delete(String statement) {    return update(statement, null);}

于是察看update即可:

int update(MappedStatement ms, Object parameter) throws SQLException {    ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());    if (closed) {      throw new ExecutorException("Executor was closed.");    }    // == 清理一级缓存    clearLocalCache();    return doUpdate(ms, parameter);}

二级缓存

二级缓存须要关上开关:

  • 第1步
<setting name="cacheEnabled" value="STATEMENT"/>
  • 第二步

同时在mapper.xml中减少标签

<cache/> 

默认的,二级缓存的key是namespace,如果要援用其它命名空间的Cache配置,能够应用如下标签:

<cache-ref namespace="xxx"/>

CachingExecutor

二级缓存的入口在executor创立地位:

public Executor newExecutor(Transaction transaction, ExecutorType executorType) {    Executor executor;    if (ExecutorType.BATCH == executorType) {      executor = new BatchExecutor(this, transaction);    } else if (ExecutorType.REUSE == executorType) {      executor = new ReuseExecutor(this, transaction);    } else {      // 默认创立SimpleExecutor      executor = new SimpleExecutor(this, transaction);    }    if (cacheEnabled) {      // == 开启二级缓存状况,应用装璜器模式用CachingExecutor包了一层      executor = new CachingExecutor(executor);    }    return executor;}

察看结构器里做了什么

// 属性相互赋值public CachingExecutor(Executor delegate) {  this.delegate = delegate;  delegate.setExecutorWrapper(this);}

赋值后CachingExecutor和SimpleExecutor的关系如下

晓得这一点后,咱们来查看CachingExecutor的query办法:

public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {    BoundSql boundSql = ms.getBoundSql(parameterObject);    // == 调用delegate的createCacheKey办法(后面曾经剖析过)    CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);    // == 二级缓存的查问    return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);}

察看query办法的实现

public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)      throws SQLException {    // ## A.通过MappedStatement获取cache    Cache cache = ms.getCache();    if (cache != null) {      // 缓存刷新      flushCacheIfRequired(ms);      if (ms.isUseCache() && resultHandler == null) {        ensureNoOutParams(ms, boundSql);        // -- 1.通过tcm获取查问后果        List<E> list = (List<E>) tcm.getObject(cache, key);        if (list == null) {          // -- 2.tcm中无后果,通过原executor查问(一级缓存+jdbc逻辑)          list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);          // -- 3.查问后果最终放入tcm中          tcm.putObject(cache, key, list);         }        return list;      }    }    return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);}// ## B.tcm指向这里TransactionalCacheManager tcm = new TransactionalCacheManager();

整顿逻辑很简略,但又有两个问题困扰到我

  1. 通过MappedStatement获取到的二级缓存cache(代码A地位),什么时候初始化的?
  2. 二级缓存和tcm(TransactionalCacheManager)之间有什么分割?

二级缓存初始化

沿着cache倒推,能追溯到Mapper解析。

残缺调用链如下(当作温习了):

// 创立SqlSessionFactoryorg.apache.ibatis.session.SqlSessionFactoryBuilder#build(java.io.Reader, java.lang.String, java.util.Properties)org.apache.ibatis.builder.xml.XMLConfigBuilder#parse// configuration解析org.apache.ibatis.builder.xml.XMLConfigBuilder#parseConfiguration// 解析mapperorg.apache.ibatis.builder.xml.XMLConfigBuilder#mapperElementorg.apache.ibatis.builder.xml.XMLMapperBuilder#parseorg.apache.ibatis.builder.xml.XMLMapperBuilder#configurationElement{    // == 二级缓存的配置援用(执行namespace)    cacheRefElement(context.evalNode("cache-ref"));    // == 二级缓存的开启    cacheElement(context.evalNode("cache"));}org.apache.ibatis.builder.xml.XMLMapperBuilder#cacheElementorg.apache.ibatis.builder.MapperBuilderAssistant#useNewCache{    // == 二级缓存创立    Cache cache = new CacheBuilder(currentNamespace)        // -- Cache实现是PerpetualCache        .implementation(valueOrDefault(typeClass, PerpetualCache.class))        // -- 包装器用了LruCache        .addDecorator(valueOrDefault(evictionClass, LruCache.class))        .clearInterval(flushInterval)        .size(size)        .readWrite(readWrite)        .blocking(blocking)        .properties(props)        .build();}

看下二级缓存的整个装璜链(盗图)

SynchronizedCache -> LoggingCache -> SerializedCache -> LruCache -> PerpetualCache。

二级缓存和TransactionalCacheManager的关系

TransactionalCacheManager类:

// ## 保护一个map,key是Cache,value是TransactionalCacheMap<Cache, TransactionalCache> transactionalCaches = new HashMap<>();public Object getObject(Cache cache, CacheKey key) {               // ## 1.此办法会在transactionalCaches中建设k-v关系    return getTransactionalCache(cache)                ⬇⬇⬇⬇⬇⬇                transactionalCaches.computeIfAbsent(cache, TransactionalCache::new);                        // == 2.从二级缓存中获取            .getObject(key);}

再察看TransactionalCache

// == 二级缓存private final Cache delegate;// == 二级缓存清理标记private boolean clearOnCommit;// ####   以下两个汇合能够了解为用来寄存长期数据   ####// == 等事务提交时,须要退出二级缓存的对象private final Map<Object, Object> entriesToAddOnCommit;// == 二级缓存中不存在的对象keyprivate final Set<Object> entriesMissedInCache;public void putObject(Object key, Object object) {    // 对象记录到entriesToAddOnCommit中    entriesToAddOnCommit.put(key, object);}public Object getObject(Object key) {    // 从二级缓存获取    Object object = delegate.getObject(key);    if (object == null) {      // 二级缓存中不存在,在entriesMissedInCache记录key      entriesMissedInCache.add(key);    }}

这里可能看出,二级缓存和Transaction(事务)有很深的瓜葛。
那么具体有什么瓜葛?

  • 事务提交

察看TransactionManager的commit办法:

org.apache.ibatis.cache.TransactionalCacheManager#commitorg.apache.ibatis.cache.decorators.TransactionalCache#commitpublic void commit() {    // == 刷新对象    flushPendingEntries();}private void flushPendingEntries() {    for (Map.Entry<Object, Object> entry : entriesToAddOnCommit.entrySet()) {      // == 对象从entriesToAddOnCommit刷新到二级缓存中      delegate.putObject(entry.getKey(), entry.getValue());    }}

此处能证实,事务提交时对象从一个长期汇合entriesToAddOnCommit刷新至二级缓存

  • 事务回滚

再察看回滚办法

org.apache.ibatis.cache.decorators.TransactionalCache#rollbackpublic void rollback() {    unlockMissedEntries();    // == 重置,将长期汇合数据清理    reset();}private void reset() {  clearOnCommit = false;  entriesToAddOnCommit.clear();  entriesMissedInCache.clear();}

附录

P6-P7常识合辑