mybatis源码中一级和二级缓存分析

文章详细阐述了MyBatis中的一级缓存和二级缓存的工作原理。一级缓存局限于同一事务,由Executor负责,在查询时优先从本地缓存中获取数据;二级缓存则可以在多个会话间共享,需在mapper.xml中配置开启,查询结果在事务提交后才保存到二级缓存。文章还介绍了缓存的失效时机及清理机制。
摘要由CSDN通过智能技术生成

mybatis中,一级缓存的作用域为一个会话内;
二级缓存的作用域为全局的,可在多个会话中使用

1、一级缓存 [此处不讨论开启二级缓存的代码逻辑]

一级缓存的作用域在同一个事物中起作用。真正执行sql的是在 Executor;类图如下;
Executor类图

1.1、生成 Executor对象的逻辑代码如下 Configuration#newExecutor
/**
下面是生成Executor的代码逻辑,如果在没有开启二级缓存的情况下,默认的实现是 
BatchExecutor | ReuseExecutor | SimpleExecutor 这三个对象
*/
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
  executorType = executorType == null ? defaultExecutorType : executorType;
  executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
  Executor executor;
  if (ExecutorType.BATCH == executorType) {
    executor = new BatchExecutor(this, transaction);
  } else if (ExecutorType.REUSE == executorType) {
    executor = new ReuseExecutor(this, transaction);
  } else {
    executor = new SimpleExecutor(this, transaction);
  }
  // 此处时开启二级缓存,在讨论一级缓存的时候假设 cacheEnable = false
  if (cacheEnabled) {
    executor = new CachingExecutor(executor);
  }
  executor = (Executor) interceptorChain.pluginAll(executor);
  return executor;
}
1.2、执行sql的时候代码逻辑. BaseExecutor#query
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
  ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
  if (closed) {
    throw new ExecutorException("Executor was closed.");
  }
  if (queryStack == 0 && ms.isFlushCacheRequired()) {
    clearLocalCache();
  }
  List<E> list;
  try {
    queryStack++;
    // 在执行sql之前,首先会 从locaCache中尝试获取一次;
    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);
    }
  } finally {
    queryStack--;
  }
  if (queryStack == 0) {
    for (DeferredLoad deferredLoad : deferredLoads) {
      deferredLoad.load();
    }
    deferredLoads.clear();
    if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
      clearLocalCache();
    }
  }
  return list;
}

/**
*  获取一级缓存中的数据:  localCache.getObject(key) 真正执行的代码逻辑;即一个 HashMap的本地缓存
*/
public Object getObject(Object key) {
   //private Map<Object, Object> cache = new HashMap<Object, Object>();
  return cache.get(key);
}
1.3、设置结果集到一级缓存中
/**
*  从数据库查询数据后设置到一级缓存中 
*/
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;
}
1.4、一级缓存失效的时机: 事物提交和事物回滚
/**
* 事物提交会清除一级缓存
*/
public void commit(boolean required) throws SQLException {
  if (closed) throw new ExecutorException("Cannot commit, transaction is already closed");
  // 清除一级缓存
  clearLocalCache();
  flushStatements();
  if (required) {
    transaction.commit();
  }
}
/**
* 事物回滚会清除一级缓存
*/
public void rollback(boolean required) throws SQLException {
  if (!closed) {
    try {
      // 清除一级缓存
      clearLocalCache();
      flushStatements(true);
    } finally {
      if (required) {
        transaction.rollback();
      }
    }
  }
}
/**
* 删除一级缓存数据
*/
public void clearLocalCache() {
  if (!closed) {
    // 将一级缓存的数据清空
    localCache.clear();
    localOutputParameterCache.clear();
  }
}

2、二级缓存

开启二级缓存需要在mapper.xml中添加 标签 ; 【默认在 config.xml中没有设置 cacheEnabled 的话,值为true】

2.1、生成Executor逻辑.如果开启二级缓存,Executor返回的对象为CachingExecutor ; Configuration#newExecutor
public Executor newExecutor(Transaction transaction, ExecutorType executorType) { 
  executorType = executorType == null ? defaultExecutorType : executorType;
  executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
  Executor executor;
  if (ExecutorType.BATCH == executorType) {
    executor = new BatchExecutor(this, transaction);
  } else if (ExecutorType.REUSE == executorType) {
    executor = new ReuseExecutor(this, transaction);
  } else {
    executor = new SimpleExecutor(this, transaction);
  }
  // 开启二级缓存
  if (cacheEnabled) {
    executor = new CachingExecutor(executor);
  }
  executor = (Executor) interceptorChain.pluginAll(executor);
  return executor;
}
2.2、执行sql的时候代码逻辑. CachingExecutor#query

public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
    throws SQLException {
// 此处需要 在mapper.xml中设置了 cache标签才会存在,否则 cache = null
  Cache cache = ms.getCache();
  // 如果设置开启了二级缓存,则先查缓存,缓存中如果有则直接返回,没有从查数据库.此处和一级缓存不一样,一级缓存会直接查到数据就缓存,而二级缓存查到后只是先临时存到了一个缓存中,在事物提交后才会缓存到二级缓存中
  if (cache != null) {
    flushCacheIfRequired(ms);
    if (ms.isUseCache() && resultHandler == null) {
      ensureNoOutParams(ms, parameterObject, boundSql);
      @SuppressWarnings("unchecked")
      /**
      * 先从缓存中取,不存在再查数据库.但请注意,此处查到后并没有直接放到二级缓存中,而是放到了一个临时缓存中
      */
      List<E> list = (List<E>) tcm.getObject(cache, key);
      if (list == null) {
        list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
        // 存放到临时缓存中,并没有真正添加到二级缓存中!!
        tcm.putObject(cache, key, list); // issue #578. Query must be not synchronized to prevent deadlocks
      }
      return list;
    }
  }
  // 如果mapper.xml没有开启二级缓存,则直接走一级缓存的执行逻辑
  return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
/**
* 获取缓存对象; TransactionalCache#getObject
*/
public Object getObject(Object key) {
  //从二级缓存中尝试获取查询结果集,多个会话在执行同一个select语句的时候,Cache对象是同一个,所以二级缓存的作用域是多个会话中可用
  Object object = delegate.getObject(key);
  if (object == null) {
    entriesMissedInCache.add(key);
  }
  if (clearOnCommit) {
    return null;
  } else {
    return object;
  }
}
2.3、将查询结果设置到二级缓存中

public void commit(boolean required) throws SQLException {
  delegate.commit(required);
  // 新增缓存
  tcm.commit();
}

/**
* TransactionalCacheManager#commit
*/
public void commit() {
  // 将本次会话中涉及到的所有缓存都添加到二级缓存中!
  for (TransactionalCache txCache : transactionalCaches.values()) {
    txCache.commit();
  }
}

/**
 * 将临时当前会话中的事物提交到二级缓存中TransactionalCache#commit()
 */
public void commit() {
  if (clearOnCommit) {
    delegate.clear();
  }
  // 真正将数据保存到二级缓存的方法
  flushPendingEntries();
  reset();
}

/**
 * 新增数据到二级缓存中
 */
private void flushPendingEntries() {
  for (Map.Entry<Object, Object> entry : entriesToAddOnCommit.entrySet()) {
      // 新增缓存
    delegate.putObject(entry.getKey(), entry.getValue());
  }
  for (Object entry : entriesMissedInCache) {
    if (!entriesToAddOnCommit.containsKey(entry)) {
      delegate.putObject(entry, null);
    }
  }
}
2.4、二级缓存中的数据移除

默认的二级缓存的Cache的调用顺序如下如所示,真正的移除操作是在 LruCache的put操作中实现的。当缓存Map中的个数 > 1024个则进行移除操作.
默认缓存流向

2.4.1、LruCache#putObject 移除二级缓存中的元素.
public void putObject(Object key, Object value) {
  delegate.putObject(key, value);
  cycleKeyList(key);
}

private void cycleKeyList(Object key) {
  keyMap.put(key, key);
  // 如果二级缓存中的元素个数超过了 1024时,移除最早的一个元素
  if (eldestKey != null) {
    delegate.removeObject(eldestKey);
    eldestKey = null;
  }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值