mybatis源码解读:cache包(缓存机制功能)

1.缓存机制

在进行源码阅读时,通常可以以包为单位进行,因为包本身就是具有一定结构、功能的类的集合,但是,也总会有一些功能相对复杂,会横跨多个包。因此以功能为主线一次阅读多个包中的源码是必要的,能帮助我们理清功能实现。

mybatis基于cache包中提供的缓存实现了两级缓存机制。

1.一级缓存

mybatis的一级缓存又叫本地缓存,与它相关的配置项有2个。

1.在配置文件XML的setting节点,可选项有session与statement,分别对应了一次会话和一条语句,一级缓存的默认范围是session。

2.是在映射文件mapper.xml中的数据库操作节点内增加flushCache属性,默认值为false。可以设置为ture或false;当设置为true时,mybatis会在改数据库操作执行前清空一,二级缓存。

一级缓存功能是由BaseExecutor类实现。BaseExecutor类作为实际执行器的基类,为所有实际执行器提供一些通用的基本功能,在这里增加缓存也就意味着每个实际执行器都具有着一级缓存。

public abstract class BaseExecutor implements Executor {
 
  private static final Log log = LogFactory.getLog(BaseExecutor.class);
 
  protected Transaction transaction;
  protected Executor wrapper;
 
  protected ConcurrentLinkedQueue<DeferredLoad> deferredLoads;
  // 查询操作的结果缓存
  protected PerpetualCache localCache;
  // Callable查询的输出参数缓存
  protected PerpetualCache localOutputParameterCache;
  protected Configuration configuration;
 
  protected int queryStack;
  private boolean closed;
/**
 * 更新数据库数据,INSERT/UPDATE/DELETE三种操作都会调用该方法
 * @param ms 映射语句
 * @param parameter 参数对象
 * @return 数据库操作结果
 * @throws SQLException
 */
@Override
public 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);
}
 
@Override
public List<BatchResult> flushStatements() throws SQLException {
  return flushStatements(false);
}
 
public List<BatchResult> flushStatements(boolean isRollBack) throws SQLException {
  if (closed) {
    throw new ExecutorException("Executor was closed.");
  }
  return doFlushStatements(isRollBack);
}
 
/**
 * 执行查询操作
 * @param ms 映射语句对象
 * @param parameter 参数对象
 * @param rowBounds 翻页限制
 * @param resultHandler 结果处理器
 * @param <E> 输出结果类型
 * @return 查询结果
 * @throws SQLException
 */
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
  BoundSql boundSql = ms.getBoundSql(parameter);
  // 生成缓存的键
  CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
  return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
 
/**
 * 查询数据库中的数据
 * @param ms 映射语句
 * @param parameter 参数对象
 * @param rowBounds 翻页限制条件
 * @param resultHandler 结果处理器
 * @param key 缓存的键
 * @param boundSql 查询语句
 * @param <E> 结果类型
 * @return 结果列表
 * @throws SQLException
 */
@SuppressWarnings("unchecked")
@Override
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++;
    // 尝试从本地缓存获取结果
    list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
    if (list != null) {
      // 本地缓存中有结果,则对于CALLABLE语句还需要绑定到IN/INOUT参数上
      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();
    // 如果本地缓存的作用域为STATEMENT,则立刻清除本地缓存
    if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
      clearLocalCache();
    }
  }
  return list;
}

因为localCache和localOutputParameterCache都是Executor的属性,而Executor归属sqlSession,因此第一级缓存的最大作用范围便是sqlSession,即一次会话。而insert,update,delete方法都对应了Executor的update方***引发一级缓存的更新。可见一级缓存就是BaseExcecutor中的两个PrepetualCache类型的属性(localCache和localOutputParameterCache),其作用范围有限,不支持各种装饰器的修饰,因此不能进行容量配置,清理策略设置及阻塞设置等。

2.二级缓存

二级缓存的作用范围是一个命名空间(即一个mapper.xml映射文件),而且可以实现多个命名空间共享一个缓存,因此比一级缓存作用范围更广,更为灵活。

二级缓存原理:

二级缓存功能由CacheingExecutor类实现,它是一个装饰器类, 能通过装饰实际执行器为它们增加二级缓存功能。mybatis会根据配置文件中的二级缓存开关配置用CacheingExecutor类装饰实际执行器。

在Configuration类中的newExecutor方法中实现用CacheingExecutor类装饰实际执行器。

/**
 * 主要内容分为以下几个部分:
 * 1、大量的配置项,和与`<configuration>`标签中的配置对应
 * 2、创建类型别名注册机,并向内注册了大量的类型别名
 * 3、创建了大量Map,包括存储映射语句的Map,存储缓存的Map等,这些Map使用的是一种不允许覆盖的严格Map
 * 4、给出了大量的处理器的创建方法,包括参数处理器、语句处理器、结果处理器、执行器。
 *    注意这里并没有真正创建,只是给出了方法。
 */
 
public class Configuration {
 
  // <environment>节点的信息
  protected Environment environment;
/**
 * 创建一个执行器
 * @param transaction 事务
 * @param executorType 数据库操作类型
 * @return 执行器
 */
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);
  }
  // 根据配置文件中settings节点cacheEnabled配置项确定是否启用缓存
  if (cacheEnabled) { // 如果配置启用缓存
    // 使用CachingExecutor装饰实际执行器
    executor = new CachingExecutor(executor);
  }
  // 为执行器增加拦截器(插件),以启用各个拦截器的功能
  executor = (Executor) interceptorChain.pluginAll(executor);
  return executor;
}