mybatis[2] executor

171 阅读3分钟

「这是我参与11月更文挑战的第9天,活动详情查看:2021最后一次更文挑战

概述

到目前为止,已经知道了:

  • executor负责的部分,是具体执行SQL并返回结果这一工作的。

实际上,executor的连接信息被封装在了内部的Transaction对象中。

我们知道,myBatis是有内置缓存这一说的,在Executor中亦有这一说法:

  • 需要注意的是,缓存一般我们是对应查询的,因此只在查询中有用到。

因此,这里我们就专注于查询这一功能以及相关的了解。

在开始代码之前,按照源码解读的顺序先看看executor的继承关系:

  • 大概的执行逻辑都被封装到了BaseExecutor中,而具体到每一项的执行逻辑,都用以do开头的方法,作为钩子提供给了子类进行实现。

    单独实现的子类包括:

    • BatchExecutor
    • ReuseExecutor
    • SimpleExecutor

这里就以SimpleExecutor为例,简单分析以下query的流程。

查询

在BaseExecutor中,mybatis执行了缓存的处理,具体代码如下:

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) {
      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();
    }
    // issue #601
    deferredLoads.clear();
    if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
      // issue #482
      clearLocalCache();
    }
  }
  return list;
}
​
  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;
  }

这里需要注意到:

  • 本地缓存是通过map来进行原生保存的,因此在当前一个服务动不动布个多台的情况下,不做扩展,如果要做实时的查询,实际上基本没法用。

  • 这里有一个queryStack,实际上这也是针对缓存的一个相关操作,标识当前执行中的SQL有多少。只有在当前没有SQL执行的时候,才回去更新缓存。

    关联的还有一个叫DeferredLoad的类,保存的是每次查询的相关信息,实际上这些只在CachingExecutor中使用。只有在cachingExecutor中,缓存才分了这么多类,其他的其实都只是做了key到结果的映射。

接下来来看看子类SimpleExecutor具体的doQuery

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 handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
    stmt = prepareStatement(handler, ms.getStatementLog());
    return handler.query(stmt, resultHandler);
  } finally {
    closeStatement(stmt);
  }
}

这里就发现了一个有趣的事实:

  • 实际上最后一步的执行,是分配到了statementHandler中来具体执行的,executor实际上是一个抽象的执行层,下面还有一个statementHandler

看一下这一块,这里能决定最后执行的handler是哪一个:

StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
​
  public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
    statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
    return statementHandler;
  }
  
    private final StatementHandler delegate;
​
  public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
​
    switch (ms.getStatementType()) {
      case STATEMENT:
        delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      case PREPARED:
        delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      case CALLABLE:
        delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      default:
        throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
    }
​
  }

再看一下这个RoutingStatementHandler下面的东西都是通过这个delegate执行的,恍然大悟:用的还是Simple。

那接下来就到了SimpleExecutor中的query了:

public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
  String sql = boundSql.getSql();
  statement.execute(sql);
  return resultSetHandler.handleResultSets(statement);
}

在普通的JDBC中,我们的结果是从statement里获取的,这里没有变。

这里的resultSetHandler,实际上就是我们定义的resultMapping;在这里就能看到,SQL的查询结果是如何变成对象的了。

之前做过excel的导入导出相关工作,如果自己写不用框架的话也是做一对一的mapping,其实这里也是。