MyBatis  执行器

MyBatis 执行器

前面介绍了 MyBatis 会话相关的知识,其中创建 SqlSession 实例的过程中会创建一个 Executor 实例,一个会话会持有一个执行器。这篇文章就介绍一下执行器相关的知识。

创建执行器

执行器是通过 Configuration#newExecutor 方法创建的

  public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    //未指定执行器类型则取默认执行器类型
    executorType = executorType == null ? defaultExecutorType : executorType;
    //默认执行器类型未设置则取 ExecutorType.SIMPLE
    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);
    }
    //开启二级缓存则创建 CachingExecutor
    if (cacheEnabled) {
      executor = new CachingExecutor(executor);
    }
    //过一遍拦截器链(插件)
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }
  1. 根据执行器类型创建执行器,优先级:指定类型 > 配置的默认类型> ExecutorType.SIMPLE类型
  2. 如果开启了二级缓存会创建一个CachingExecutor 我所用的 MyBatis 3.5.0 版本 cacheEnabled 值默认是 ture,所以这里创建的就是 CachingExecutor,也可以通过设置 <setting name="cacheEnabled" value="false"/> 来关闭二级缓存,关于缓存后面会单独介绍。
  3. 最后会走一遍拦截器链,其实就是配置的插件,关于插件后面会单独介绍。

执行器分类

mybatis-executor

  • CachingExecutor 使用装饰器模式,内部持有另外一个 Executor,用于二级缓存未命中时查询。
  • BaseExecutor 使用模板模式,其子类的操作都以该类为入口,查询逻辑最终会调用 doQuery 方法,更新操作最终都会调用 doUpdate 方法,这些都交由其子类实现。
  • SimpleExecutor 默认的简单执行器
  • BatchExecutor 用来支撑批量操作的批量执行器
  • ReuseExecutor 通过内部维护一个 statementMap 达到重用 Statement 的目的

执行器是干嘛的

我们知道 SqlSession 实例会持有一个 Executor 实例,那么执行器是不是和会话的 CURD 操作有关呢?是的,SqlSession 的查询操作都会委托给 Executorquery 方法,增删改操作都会通过执行器的 update 方法完成。那就拿 selectList 来看看具体是怎么实现的吧。

  //org.apache.ibatis.session.defaults.DefaultSqlSession

  public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
      MappedStatement ms = configuration.getMappedStatement(statement);
      return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

首先通过 statement 查询对应的 MappedStatement,这里的 statement 其实就是 Mapper 接口方法的全限定名,MappedStatement 是 mapper.xml 文件中与之对应的 SQL。为了保证能唯一对应,所以 Mapper 接口中方法不支持重载
接着就是 Executor 发乎作用的地方了,这里先不考虑开启二级缓存的情况,所以来看看 BaseExecutor#query 方法逻辑。

public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    ......
    List<E> list;
    try {
      queryStack++;
      //和一级缓存相关,暂不用了解
      list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
      if (list != null) {
        handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
      } else {
        //一级缓存中不存在,查 DB
        list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
      }
    } finally {
      queryStack--;
    }
    ......
    return list;
  }

直接看查询 DB 的逻辑

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;
  }

最终实际查询的逻辑交由子类去实现 doQuery 方法来完成,就拿默认的 SimpleExecutor#doQuery 来看看吧

  @Override
  public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Statement stmt = null;
    try {
      Configuration configuration = ms.getConfiguration();
      //构造一个 StatementHandler
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      stmt = prepareStatement(handler, ms.getStatementLog());
      return handler.query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }

看到这里就要引入 MyBatis 中另外一个比较重要的组件 StatementHandler 了,关于它后面会单独介绍。

总结

关于执行器,只需要知道 SqlSession 的 CRUD 操作都会委托给 Executor 去执行,记住几种执行器的类型。实际代码中会涉及到一级、二级缓存相关的知识,并且最终的操作还是交给了 StatementHandler 去完成的,所以等把这两块知识介绍完后,整条链路就清晰了。