mybatis 缓存(三)

mybatis的缓存分为一级缓存和二级缓存java

一级缓存:基于SqlSession级别的缓存,也就是说,缓存了这个SqlSession执行全部的select.MapperStatement的结果集;同一个查询语句,只会请求一次;可是当前SqlSession执行增删改操做或者commit/rollback操做时,会清空SqlSession的一级缓存;sql

禁止一级缓存(同理也禁止了二级缓存)数据库

xml方式:
<select id="ping" flushCache="true" resultType="string">
...
</select>

注解方式:
@Options(flushCache = FlushCachePolicy.TRUE)

一级缓存致使的问题:每一个SqlSession可能会对同一个mapperStatement缓存不一样的数据,如:设计模式

  • sqlSession1 查询了userId=1的数据,一级缓存生效
  • sqlSession2更新了userId=1的name值,而后在查询,一级缓存生效

这致使了sqlSession1和sqlSession2对于userId=1的缓存数据不一致,引入脏数据缓存

一级缓存源代码:安全

public abstract class BaseExecutor implements Executor {
  // 一级缓存
  protected PerpetualCache localCache;
  protected BaseExecutor(Configuration configuration, Transaction transaction) {
    ...
    // 默认使用PerpetualCache
    this.localCache = new PerpetualCache("LocalCache");
    ...
  }

  @Override
  public int update(MappedStatement ms, Object parameter) throws SQLException {
    ...
    // 增删改删除一级缓存
    clearLocalCache();
    ...
  }

  @SuppressWarnings("unchecked")
  @Override
  public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    ...
    // flushCache=true时清空一级缓存
    if (queryStack == 0 && ms.isFlushCacheRequired()) {
      clearLocalCache();
    }
    ...
    // 判断一级缓存是否有值
    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);
    }
    ...
  }

  @Override
  public boolean isCached(MappedStatement ms, CacheKey key) {
    // 一级缓存是否包含cachekey
    return localCache.getObject(key) != null;
  }

  @Override
  public void commit(boolean required) throws SQLException {
    ...
    // commit 删除缓存
    clearLocalCache();
    ...
  }

  @Override
  public void rollback(boolean required) throws SQLException {
    ...
    // rollback删除缓存
    clearLocalCache();
    ...
  }

  @Override
  public void clearLocalCache() {
    if (!closed) {
      // 清空缓存
      localCache.clear();
      localOutputParameterCache.clear();
    }
  }

  private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    ...
    try {
      list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
    } finally {
      // 删除原来的一级缓存
      localCache.removeObject(key);
    }
    // 将新获取的值放入一级缓存
    localCache.putObject(key, list);
    。。。
  }
}

二级缓存:基于SqlSessionFactory缓存,全部SqlSession查询的结果集都会共享;其实这样描述是不许确的,二级缓存是基于namespace的缓存,每一个mapper对应一个全局Mapper namespace;当第一个Sqlsession查出的结果集,缓存在namespace中,第二个sqlsession再查找时会从nameSpace中获取;每一个namespace是单例的;只有sqlSession调用了commit方法才会生效session

禁止二级缓存:mybatis

mybatis-congif.xml:将全部的namespace都关闭二级缓存
     <settings>
        <setting name="cacheEnabled" value="false"/>
        ...
     </settings>

对单个namespace是否使用二级缓存
    <cache /> 当前namespace是否使用二级缓存
    <cache-ref namespace="..." /> 当前namespace和其它namespace共用缓存

对一个namespace中的单个MapperStatement关闭二级缓存
    <select id="selectParentBeans" resultMap="ParentBeanResultMap" useCache="false">
        select * from parent
    </select>

二级缓存致使的问题:当某个namespace出现多表查询时,会引发脏数据,如:app

  • A namespace中的mapperStatement id = "xxx"查询了表A id=1和表B id=5的数据,二级缓存生效
  • 表B 更新了id=5的数据;

此时再来查A表中的mapperStatement id = "xxx"时,仍是使用了A namespace中的二级缓存;这引发了B表id=5的脏数据ide

固然解决上述问题能够使用<cache-ref>;但这会致使缓存粒度变粗,多个namespace的操做都会影响该缓存;

二级缓存源码:

Configuration#
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
  ...
  // 判断是否使用二级缓存
  if (cacheEnabled) {
    executor = new CachingExecutor(executor);
  }
  // 对executor制定插件
  executor = (Executor) interceptorChain.pluginAll(executor);
  return executor;
}
public class CachingExecutor implements Executor {

  // 委托设计模式
  private final Executor delegate;
  // 二级缓存
  private final TransactionalCacheManager tcm = new TransactionalCacheManager();

  @Override
  public int update(MappedStatement ms, Object parameterObject) throws SQLException {
    // 更新调用清空缓存方法(flushCache=false时不状况二级缓存)
    flushCacheIfRequired(ms);
    return delegate.update(ms, parameterObject);
  }

  @Override
  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
      throws SQLException {
    // 判断该namespace是否含有缓存
    Cache cache = ms.getCache();
    if (cache != null) {
      // 看是否须要清空该namespace下的二级缓存
      flushCacheIfRequired(ms);
      // 当前MapperStatement是否须要使用二级缓存
      if (ms.isUseCache() && resultHandler == null) {
          ...
          list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
          // 存入二级缓存
          tcm.putObject(cache, key, list); // issue #578 and #116
          ...
      }
    }
  }

  private void flushCacheIfRequired(MappedStatement ms) {
    Cache cache = ms.getCache();
    // flushCache=true时清空该namespace下的二级缓存,反之则不状况缓存
    if (cache != null && ms.isFlushCacheRequired()) {
      tcm.clear(cache);
    }
  }
}
public class TransactionalCacheManager {
  // 存储二级缓存,以namespace的cache做为key
  private final Map<Cache, TransactionalCache> transactionalCaches = new HashMap<>();
  // 清空namespace的缓存
  public void clear(Cache cache) {
    getTransactionalCache(cache).clear();
  }
  // 获取某个namespace中的cacheKey的缓存
  public Object getObject(Cache cache, CacheKey key) {
    return getTransactionalCache(cache).getObject(key);
  }
  // 放入某个namespace cacheKey的缓存
  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();
    }
  }
  // transactionalCaches缓存有则不存储,没有则存入
  private TransactionalCache getTransactionalCache(Cache cache) {
    return transactionalCaches.computeIfAbsent(cache, TransactionalCache::new);
  }

}

在看源代码的时候,transactionalCaches引发深思

  • 每一个sqlSession中有新的CachingExecutor
  • 每一个CachingExecutor有新的TransactionalCacheManager
  • TransactionalCacheManager中的transactionalCaches是每一个sqlSession独享的,如何达到线程安全且多个sqlSession共享呢?

其实二级缓存是以namespace粒度存储在Mapper里面的;每一个mapper是全局共享的;并且getTransactionalCache这个方法已经将当前的namespace存放于每一个cachingExecutor中了,因此达到了线程安全且sqlSession共享

相关文章
相关标签/搜索