mybatis之Executor

Executor是MyBatis的核心接口之一,其中定义了数据库操作的基本方法。在实际应用中经常涉及的SqlSession接口的功能,都是基于Executor接口实现的。Executor接口中定义的方法如下:

public interface Executor {

  ResultHandler NO_RESULT_HANDLER = null;

  // 执行insert、update、delete等操作
  int update(MappedStatement ms, Object parameter) throws SQLException;

  // 执行 select类型的SQL语句,返回位分为结果对象列表或游标对象
  <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql) throws SQLException;

  <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException;

  <E> Cursor<E> queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) throws SQLException;

  // 批量执行SQL语句
  List<BatchResult> flushStatements() throws SQLException;

  // 提交事务
  void commit(boolean required) throws SQLException;

  // 回滚事务
  void rollback(boolean required) throws SQLException;

  // 创建缓存中用的CacheKey对象
  CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql);

  // 根据CacheKey对象查找缓存
  boolean isCached(MappedStatement ms, CacheKey key);

  // 清空一级缓存
  void clearLocalCache();

  // 延迟加载一级缓存中的数据
  void deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key, Class<?> targetType);

  // 获取事务对象
  Transaction getTransaction();

  // 关闭Executor对象
  void close(boolean forceRollback);

  // 检测Executor是否已关闭
  boolean isClosed();

  void setExecutorWrapper(Executor executor);

}

1.BaseExecutor

BaseExecutor是一个实现了Executor接口的抽象类,它实现了Executor接口的大部分方法,其中就使用了模板方法模式。BaseExecutor主要提供了缓存管理和事务管理的基本功能,继承BaseExecutor的子类只要实现四个基本方法来完成数据库的相关操作即可,这四个方法分别doUpdate()方法、doQuery()方法、doQueryCursor()方法、doFlushStatement()方法,其余的功能在BaseExecutor实现。

BaseExecutor各个字段的含义如下:

  // Transaction对象,实现事务的提交、回滚和关闭操作
  protected Transaction transaction;
  // 封装的Executor对象
  protected Executor wrapper;

  // 延迟加载队列
  protected ConcurrentLinkedQueue<DeferredLoad> deferredLoads;
  // 一级缓存,用于缓存该Executor对象查询结果集映射得到的结果对象
  protected PerpetualCache localCache;
  // 一级缓存,用于缓存输出类型的参数
  protected PerpetualCache localOutputParameterCache;

  // 用来记录嵌套查询的层数
  protected int queryStack;

一级缓存

执行 select 语句查询数据库是最常用的功能,BaseExecutor.query()方法会首先创建CacheKey对象,并根据该CacheKey对象查找一级缓存,如果缓存命中则返回缓存中记录的结果对象,如果缓存未命中则查询数据库得到结果集,之后将结果集映射成结果对象并保存到一级缓存中,同时返回结果对象。query()方法的具体实现如下:

@Override
  public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    // 获取BoundSql对象
    BoundSql boundSql = ms.getBoundSql(parameter);
    // 创建CacheKey对象
    CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
    // 调用query()的一个重载
    return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
  }

这里关注BaseExecutor.createCacheKey()方法创建的CacheKey对象由哪几部分构成,createCacheKey()方法具体实现如下:

 @Override
  public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
    // 检测Executor是否关闭
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    CacheKey cacheKey = new CacheKey();
    // 添加MappedStatement的id
    cacheKey.update(ms.getId());
    // 添加offset
    cacheKey.update(rowBounds.getOffset());
    // 添加limit
    cacheKey.update(rowBounds.getLimit());
    // 添加SQL语句
    cacheKey.update(boundSql.getSql());
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();
    // mimic DefaultParameterHandler logic
    // 获取实参并添加到CacheKey
    for (ParameterMapping parameterMapping : parameterMappings) {
      if (parameterMapping.getMode() != ParameterMode.OUT) {
        Object value;
        String propertyName = parameterMapping.getProperty();
        if (boundSql.hasAdditionalParameter(propertyName)) {
          value = boundSql.getAdditionalParameter(propertyName);
        } else if (parameterObject == null) {
          value = null;
        } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
          value = parameterObject;
        } else {
          MetaObject metaObject = configuration.newMetaObject(parameterObject);
          value = metaObject.getValue(propertyName);
        }
        cacheKey.update(value);
      }
    }
    // 添加Environment的id
    if (configuration.getEnvironment() != null) {
      // issue #176
      cacheKey.update(configuration.getEnvironment().getId());
    }
    return cacheKey;
  }

由此可知该CacheKey对象由MappedStatement的id 、对应的offset、limit、SQL语句(包含?占位符)、用户传递的实参以及Environment.id这五部分构成。

继续来看上述代码中调用的query()方法的另一重载的具体实现,该重载会根据前面创建的CacheKey对象查询一级缓存,如果缓存命中则将缓存中记录的结果对象返回,如果缓存未命中,则调用 doQuery()方法完成数据库的查询操作并得到结果对象,之后将结果对象记录到一级缓存中。具体实现如下:

@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());
    // 检测Executor是否关闭
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    if (queryStack == 0 && ms.isFlushCacheRequired()) {
      // 非嵌套查询,并且节点中的useCache属性,该属性表示查询操作产生的结果对象是否要保存到二级缓存中。useCache属性的默认值是true。

TransactionalCache&TransactionalCacheManager

TransactionalCache和TransactionalCacheManager是CachingExecutor依赖的两个组件。其中,TransactionalCache继承了Cache接口,主要用于保存在某个SqlSession的某个事务中需要向某个二

级缓存中添加的缓存数据。TransactionalCache中核心字段的含义如下:

 // 底层封装的二级缓存所对应的Cache对象
  private final Cache delegate;
  // 当该字段为true时,则表示当前TransactionalCache不可查询,且提交事务时会将底层Cache清空
  private boolean clearOnCommit;
  // 暂时记录添加到TransactionalCache中的数据。在事务提交时,会将其中的数据添加到二级缓存中
  private final Map<Object, Object> entriesToAddOnCommit;
  // 记录缓存未命中的CacheKey对象
  private final Set<Object> entriesMissedInCache;

TransactionalCache.putObject()方法并没有直接将结果对象记录到其封装二级缓存中,而是暂时保存在 entriesToAddOnCommit集合中,在事务提交时才会将这些结果对象从entriesToAddOnCommit集合添加到二级缓存中。 putObject()方法的具体实现如下:

  @Override
  public void putObject(Object key, Object object) {
    entriesToAddOnCommit.put(key, object);
  }

再来看TransactionalCache.getObject()方法,它首先会查询底层的二级缓存,并将未命中的key添加到entriesMissedInCache中,之后会根据clearOnCommit字段的值决定具体的返回值,具体实现如下:

@Override
  public Object getObject(Object key) {
    // issue #116
    Object object = delegate.getObject(key);
    if (object == null) {
      // 将未命中的key添加到entriesMissedInCache中
      entriesMissedInCache.add(key);
    }
    // issue #146
    // 根据clearOnCommit字段的值决定具体的返回值
    if (clearOnCommit) {
      return null;
    } else {
      return object;
    }
  }

TransactionalCache.clear()方法会清空entriesToAddOnCommit集合,并设置 clearOnCommi为true 。

TransactionalCache. commit()方法会根据clearOnCommit字段的值决定是否清空二级缓存,然后调用 flushPendingEntries()方法将entriesToAddOnCommit集合中记录的结果对象保存到二级缓存中,具体实现如下:

 public void commit() {
    // 在事务提交前 清空二级缓存
    if (clearOnCommit) {
      delegate.clear();
    }
    // 将entriesToAddOnCommit集合中的数据保存到二级缓存
    flushPendingEntries();
    // 重置clearOnCommit为false ,并清空entriesToAddOnCommit和entriesMissedInCache 集合
    reset();
  }
 private void flushPendingEntries() {
    // 遍历entriesToAddOnCommit集合,将其中记录的缓存项添加到二级缓存中
    for (Map.Entry<Object, Object> entry : entriesToAddOnCommit.entrySet()) {
      delegate.putObject(entry.getKey(), entry.getValue());
    }
    // 遍历entriesMissedInCache集合,将entriesToAddOnCommit集合中不包含的缓存项添加到二级缓存中
    for (Object entry : entriesMissedInCache) {
      if (!entriesToAddOnCommit.containsKey(entry)) {
        delegate.putObject(entry, null);
      }
    }
  }

TransactionalCache.rollback()方法会将entriesMissedlnCache集合中记录的缓存项从二级缓存中删除,并清空entriesToAddOnCommit集合和entriesMissedlnCache集合,具体实现如下:

public void rollback() {
    // 将entriesMissedInCache集合中记录的缓存项从二级缓存中删除
    unlockMissedEntries();
    // 遍历entriesMissedInCache集合,将entriesToAddOnCommit集合中不包含的缓存项添加到二级缓存中
    reset();
  }

TransactionalCacheManager用于管理CachingExecutor使用的二级缓存对象,其中只定义了transactionalCaches字段,它的 key 是对应的CachingExecutor使用的 二级缓存对象,value是相应TransactionaCach对象,在该TransactionalCache中封装了对应二级缓存对象,也就是这里的 key。

TransactionalCacheManager的实现比较简单,下面简单介绍各个方法的功能和实现。

*clear()方法、 putObject方法、 getObject()方法:*调用指定二级缓存对应的TransactionalCache对象的对应方法,如果transactionalCaches集合中没有对应的TransactionalCache对象,则通过getTransactiona!Cache()方法创建。

  private TransactionalCache getTransactionalCache(Cache cache) {
    // 如果transactionalCaches集合中没有对应的TransactionalCache对象,则新建
    return transactionalCaches.computeIfAbsent(cache, TransactionalCache::new);
  }

*commit()方法、 rollback()方法:*遍历transactionalCaches集合,井调用其中各个TransactionalCache对象的相应方法。

CachingExecutor的实现

CachingExecutor.query()方法执行查询操作的步骤如下:

(1)获取BoundSql对象,创建查询语句对应的CacheKey对象。

(2)检测是否开启了二级缓存,如果没有开启二级缓存,则直接调用底层Executor对象的query()方法查询数据库。如果开启了二级缓存,则继续后面的步骤。

(3)检测查询操作是否包含输出类型的参数,如果是这种情况,则报错

(4)调用TransactionalCacheManager.getObject()方法查询二级缓存,如果二级缓存中查找到相应的结果对象,则直接将该结果对象返回。

(5)如果二级缓存没有相应的结果对象,则调用底层Executor对象的query()方法。正如前面介绍的 ,它会先查询一级缓存,一级缓存未命中时,才会查询数据库。最后还会将得到的结果对象放入TransactionalCache.entriesToAddOnCommit集合中保存。

CachingExecutor.query()方法的具体代码如下:

@Override
  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    // 获取BoundSql对象
    BoundSql boundSql = ms.getBoundSql(parameterObject);
    // 创建CacheKey对象
    CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
    return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }
 @Override
  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
      throws SQLException {
    // 获取查询语句所在命名空间对应的二级缓存
    Cache cache = ms.getCache();
    // :是否开启了二级缓存功能
    if (cache != null) {
      // 根据