Mybatis源码解读系列(五)-Executor

434 阅读8分钟

这一篇我们再来梳理Mybatis中的Executor接口及其对应的实现类。

上一篇我们主要了解的是StatementHanlder,现在我们来了解另一个接口Executor执行器,前面的StatementHanlder就是在这里创建以及使用的。

一、Executor的结构

public interface Executor {

  ResultHandler NO_RESULT_HANDLER = null;
  int update(MappedStatement ms, Object parameter) throws SQLException;
  <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;

  List<BatchResult> flushStatements() throws SQLException;

  void commit(boolean required) throws SQLException;
  void rollback(boolean required) throws SQLException;
  CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql);
  boolean isCached(MappedStatement ms, CacheKey key);
  void clearLocalCache();
  void deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key, Class<?> targetType);

  Transaction getTransaction();
  void close(boolean forceRollback);
  boolean isClosed();
  void setExecutorWrapper(Executor executor);
}

这里定义了一些sql执行的方法,事务相关等的内容,下面我们集体来看其的实现类是怎样实现的。

二、BaseExecutor

1、结构

public abstract class BaseExecutor implements Executor {

​ 这个就是关于Executor实现相关的基类了。

2、成员变量

1)、transaction

protected Transaction transaction;

​ 这个是对应的事务操作的,例如事务回滚。

2)、wrapper

protected Executor wrapper;

​ 这个也是一个Executor,所以我们可以在一个Executor中包括另一个Executor。

3)、deferredLoads

protected ConcurrentLinkedQueue<DeferredLoad> deferredLoads;

​ 这个是与延迟操作相关的,我们忽略,目前还没有遇到用mybatis主动去进行这种操作。

4)、queryStack

protected int queryStack;

​ 看mybatis对其的自加操作,我觉得其实可以用这个值来判断对db查询操作的并发数量。

​ 还有其它的几个与缓存相关的内容,就不列举了。

3、构造方法

protected BaseExecutor(Configuration configuration, Transaction transaction) {
  this.transaction = transaction;
  this.deferredLoads = new ConcurrentLinkedQueue<>();
  this.localCache = new PerpetualCache("LocalCache");
  this.localOutputParameterCache = new PerpetualCache("LocalOutputParameterCache");
  this.closed = false;
  this.configuration = configuration;
  this.wrapper = this;
}

​ 这个就是创建对前面的变量的初始化,可以看到目前wrapper的值直接是this。

1)、getTransaction()

@Override
public Transaction getTransaction() {
  if (closed) {
    throw new ExecutorException("Executor was closed.");
  }
  return transaction;
}

​ 获取Transaction。

2)、 close(boolean forceRollback)

@Override
public void close(boolean forceRollback) {
  try {
    try {
      rollback(forceRollback);
    } finally {
      if (transaction != null) {
        transaction.close();
      }
    }
  } catch (SQLException e) {
    // Ignore. There's nothing that can be done at this point.
    log.warn("Unexpected exception on closing transaction.  Cause: " + e);
  } finally {
    transaction = null;
    deferredLoads = null;
    localCache = null;
    localOutputParameterCache = null;
    closed = true;
  }
}

​ 这个是关闭当前的执行器,然后入参forceRollback,是表示,在关闭的时候是否直接回滚事务。

3)、isClosed()

@Override
public boolean isClosed() {
  return closed;
}

​ 判断当前执行器是否关闭。

4)、update(MappedStatement ms, Object parameter)

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

这个就是执行更新操作,可以看到其的入参是MappedStatement,以及需要传给sql语句的参数。这里会先去清下缓存

5)、query(...)

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

​ 查询操作,调用的另一个query方法。

6)、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());
  ......
  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--;
  }
  ......
  return list;
}

​ 上面我省略了一些关于延迟加载&缓存相关的内容。可以看到其首先是从缓存中去获取,没有的话再调用queryFromDatabase方法去查询DB。这里我们可以看到一个其有一个try-finally,首先会queryStack++,然后在queryStack--,同时queryStack是成员变量,所以其可以统计总是查询峰值(当然可能由于并发出现少统计的情况)。

7)、queryCursor(...)

@Override
public <E> Cursor<E> queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) throws SQLException {
  BoundSql boundSql = ms.getBoundSql(parameter);
  return doQueryCursor(ms, parameter, rowBounds, boundSql);
}

​ 这个就是去执行存储过程,我们忽略。

8)、commit(boolean required)

@Override
public void commit(boolean required) throws SQLException {
  if (closed) {
    throw new ExecutorException("Cannot commit, transaction is already closed");
  }
  clearLocalCache();
  flushStatements();
  if (required) {
    transaction.commit();
  }
}

​ 提交事务。

9)、rollback(boolean required)

@Override
public void rollback(boolean required) throws SQLException {
  if (!closed) {
    try {
      clearLocalCache();
      flushStatements(true);
    } finally {
      if (required) {
        transaction.rollback();
      }
    }
  }
}

​ 回滚事务及前缓存。flushStatements方法一般是供批量操作的Executor使用的。

10)、setExecutorWrapper(Executor wrapper)

@Override
public void setExecutorWrapper(Executor wrapper) {
  this.wrapper = wrapper;
}

​ 设置wrapper,其默认是this。

11)、queryFromDatabase(...)

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 {
    ......
  return list;
}

​ 调用doQuery方法去查询DB。

12)、getConnection(Log statementLog)

protected Connection getConnection(Log statementLog) throws SQLException {
  Connection connection = transaction.getConnection();
  if (statementLog.isDebugEnabled()) {
    return ConnectionLogger.newInstance(connection, statementLog, queryStack);
  } else {
    return connection;
  }
}

​ 获取Connection。

13)、抽象方法

protected abstract int doUpdate(MappedStatement ms, Object parameter) throws SQLException;

protected abstract List<BatchResult> doFlushStatements(boolean isRollback) throws SQLException;

protected abstract <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)
    throws SQLException;

protected abstract <E> Cursor<E> doQueryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds, BoundSql boundSql)
    throws SQLException;

​ 然后我们看到这些方法都是抽象方法供其子类实现。

三、SimpleExecutor

1、结构&构造方法

public class SimpleExecutor extends BaseExecutor {

  public SimpleExecutor(Configuration configuration, Transaction transaction) {
    super(configuration, transaction);
  }

​ 其继承BaseExecutor,然后直接代用的其的父类构造方法。

2、方法

1)、doUpdate(MappedStatement ms, Object parameter)

@Override
public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
  Statement stmt = null;
  try {
    Configuration configuration = ms.getConfiguration();
    StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
    stmt = prepareStatement(handler, ms.getStatementLog());
    return handler.update(stmt);
  } finally {
    closeStatement(stmt);
  }
}

​ 可以看到其在这里创建我们上一篇梳理的StatementHandler,再通过prepareStatement方法获取Statement,然后再通过StatementHandler去调用Statement的执行方法。然后调用StatementHandler的update去通过其的Statement去执行。

2)、newStatementHandler(...) Configuration

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

​ 其创建RoutingStatementHandler,这里也会去执行Interceptor。而RoutingStatementHandler我们上篇梳理过,其是根据StatementType创建对应的StatementHandler,所以我们这里一般创建的是SimpleStatementHandler。

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

3)、prepareStatement(StatementHandler handler, Log statementLog)

private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
  Statement stmt;
  Connection connection = getConnection(statementLog);
  stmt = handler.prepare(connection, transaction.getTimeout());
  handler.parameterize(stmt);
  return stmt;
}

​ 这里就是先获取Connection,然后再通过StatementHandler获取初始化创建Statement,再用StatementHandler去处理对应的参数设置。

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

​ 这里的主要流程与前面的doUpdate方法类似,不过其调用的是StatementHandler的query方法。

四、BatchExecutor

​ 这个是用来批处理的,所以其主要是用来解析更新操作去进行批处理。这里需要先了解JDBC关于批处理的操作,主要是两个方法:addBatch&executeBatch。

1、结构&构造方法

public class BatchExecutor extends BaseExecutor {
public BatchExecutor(Configuration configuration, Transaction transaction) {
  super(configuration, transaction);
}

2、成员变量

1)、statementList

private final List<Statement> statementList = new ArrayList<>();

​ 这个就是用来放批处理的statementList的。

2)、batchResultList

​ 用来放批处理的结果的。

3)、currentSql

private String currentSql;

​ 当前的sql语句。

4)、currentStatement

private MappedStatement currentStatement;

​ 这个就是与对应sql执行需要的整体内容信息。

3、方法

1)、doUpdate(MappedStatement ms, Object parameterObject)

@Override
public int doUpdate(MappedStatement ms, Object parameterObject) throws SQLException {
  final Configuration configuration = ms.getConfiguration();
  final StatementHandler handler = configuration.newStatementHandler(this, ms, parameterObject, RowBounds.DEFAULT, null, null);
  final BoundSql boundSql = handler.getBoundSql();
  final String sql = boundSql.getSql();
  final Statement stmt;
  if (sql.equals(currentSql) && ms.equals(currentStatement)) {
    int last = statementList.size() - 1;
    stmt = statementList.get(last);
    applyTransactionTimeout(stmt);
    handler.parameterize(stmt);// fix Issues 322
    BatchResult batchResult = batchResultList.get(last);
    batchResult.addParameterObject(parameterObject);
  } else {
    Connection connection = getConnection(ms.getStatementLog());
    stmt = handler.prepare(connection, transaction.getTimeout());
    handler.parameterize(stmt);    // fix Issues 322
    currentSql = sql;
    currentStatement = ms;
    statementList.add(stmt);
    batchResultList.add(new BatchResult(ms, sql, parameterObject));
  }
  handler.batch(stmt);
  return BATCH_UPDATE_RETURN_VALUE;
}
public static final int BATCH_UPDATE_RETURN_VALUE = Integer.MIN_VALUE + 1002;

​ 这个就是对应的批处理更新操作。首先是获取StatementHandler,然后通过sql.equals(currentSql) && ms.equals(currentStatement)判断批处理操作时同一个Statement,再从获取statementList获取Statement,再获取对应的批处理的

BatchResult返回对象。

public class BatchResult {

  private final MappedStatement mappedStatement;
  private final String sql;
  private final List<Object> parameterObjects;
    ......
  public void addParameterObject(Object parameterObject) {
    this.parameterObjects.add(parameterObject);
  }

​ 上面梳理的是能获取到当前的currentSql,如果不能获取到,就需要再创建对应的statement&BatchResult。这些解决了,最后才是一

handler.batch(stmt)去调用batch操作。

@Override
public void batch(Statement statement) throws SQLException {
  PreparedStatement ps = (PreparedStatement) statement;
  ps.addBatch();
}

​ 同时通过这个doUpdate方法的获取,我们知道可能会出现线程安全的问题(currentSql、statementList这些都是全局变量,而获取的时候是直接获取的最后一个位置,我再想这里是不是可以使用Map比List要好点?),所以需要自己进行同步(因为这个Executor一般是SqlSession级别的)。

final Executor executor = configuration.newExecutor(tx, execType);
return new DefaultSqlSession(configuration, executor, autoCommit);

这个地方先剧透出来。

2)、doQuery(...)

@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)
    throws SQLException {
  Statement stmt = null;
  try {
    flushStatements();
    Configuration configuration = ms.getConfiguration();
    StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameterObject, rowBounds, resultHandler, boundSql);
    Connection connection = getConnection(ms.getStatementLog());
    stmt = handler.prepare(connection, transaction.getTimeout());
    handler.parameterize(stmt);
    return handler.query(stmt, resultHandler);
  } finally {
    closeStatement(stmt);
  }
}

​ 这里与SimpleExecutor是类似的,但其前面都一个关键的方法flushStatements()的调用,BatchExecutor会特别处理。

3)、doFlushStatements(boolean isRollback)

@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);
}
@Override
public List<BatchResult> doFlushStatements(boolean isRollback) throws SQLException {
  try {
    List<BatchResult> results = new ArrayList<>();
    if (isRollback) {
      return Collections.emptyList();
    }
    for (int i = 0, n = statementList.size(); i < n; i++) {
      Statement stmt = statementList.get(i);
      applyTransactionTimeout(stmt);
      BatchResult batchResult = batchResultList.get(i);
      try {
        batchResult.setUpdateCounts(stmt.executeBatch());
        MappedStatement ms = batchResult.getMappedStatement();
        List<Object> parameterObjects = batchResult.getParameterObjects();
        KeyGenerator keyGenerator = ms.getKeyGenerator();
         ......省略一些主键操作内容
        // Close statement to close cursor #1109
        closeStatement(stmt);
      } catch (BatchUpdateException e) {
        ......
        }
        throw new BatchExecutorException(message.toString(), e, results, batchResult);
      }
      results.add(batchResult);
    }
    return results;
  } finally {
    for (Statement stmt : statementList) {
      closeStatement(stmt);
    }
    currentSql = null;
    statementList.clear();
    batchResultList.clear();
  }
}

​ 这里就会执行前面在这些addBatch时并没有最好执行executeBatch方法。这里就是遍历statementList,然后获取对应的batchResultLis来得到对应的BatchResult批量结果内容,然后在通过stmt.executeBatch()执行批量操作并得到对应的执行影响结果添加到batchResultList中,所以在使用批量处理的时候,在一直调用update进行批量添加操作的时候,最后要主动调用flushStatements()方法来执行及获取结果,当然也可以执行下查询操作来自动执行这个flushStatements方法。

五、CachingExecutor

1、结构&构造方法

public class CachingExecutor implements Executor {
public CachingExecutor(Executor delegate) {
  this.delegate = delegate;
  delegate.setExecutorWrapper(this);
}

这个与前面两个执行器的实现不同的是,这个是有一个Executor来作为代理实现的。因为这个是使用缓存的执行器,其本身并没有实现一些操作逻辑,其的操作逻辑是交给delegate代理执行器来处理,其主要是处理一些与缓存相关的内容。

2、成员变量

1)、delegate

private final Executor delegate;

​ 这个就是用来执行对应操作的delegate执行器。

2)、tcm

private final TransactionalCacheManager tcm = new TransactionalCacheManager();

​ 这个是与事务想关的缓存。

3、方法

1)、update(MappedStatement ms, Object parameterObject)

@Override
public int update(MappedStatement ms, Object parameterObject) throws SQLException {
  flushCacheIfRequired(ms);
  return delegate.update(ms, parameterObject);
}

​ 例如这里的更新操作,其就是通过代理执行器去处理,其本身是调用flushCacheIfRequired方法去处理缓存。

2)、query(...)

@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
  BoundSql boundSql = ms.getBoundSql(parameterObject);
  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) {
    flushCacheIfRequired(ms);
    if (ms.isUseCache() && resultHandler == null) {
      ensureNoOutParams(ms, boundSql);
      @SuppressWarnings("unchecked")
      List<E> list = (List<E>) tcm.getObject(cache, key);
      if (list == null) {
        list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
        tcm.putObject(cache, key, list); // issue #578 and #116
      }
      return list;
    }
  }
  return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}

​ 这个查询方法也是类似。

3)、flushCacheIfRequired(MappedStatement ms)

private void flushCacheIfRequired(MappedStatement ms) {
  Cache cache = ms.getCache();
  if (cache != null && ms.isFlushCacheRequired()) {
    tcm.clear(cache);
  }
}

​ 这个就是与缓存清理相关的逻辑,我们就不具体分析了。

六、ReuseExecutor

1、结构&构造方法

public class ReuseExecutor extends BaseExecutor {
public ReuseExecutor(Configuration configuration, Transaction transaction) {
  super(configuration, transaction);
}

​ 也是直接调用的父类。这个执行器是可重用的执行器,其是对Statement重用,在使用后并不会直接关闭,而是下一次再重新执行。

2、变量

1)、statementMap

private final Map<String, Statement> statementMap = new HashMap<>();

​ 其就是通过这个map来缓存Statement的。

3、方法

1)、doUpdate(MappedStatement ms, Object parameter)

@Override
public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
  Configuration configuration = ms.getConfiguration();
  StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
  Statement stmt = prepareStatement(handler, ms.getStatementLog());
  return handler.update(stmt);
}

​ 这个其实与前面的SimpleExecutor方法类似,其主要是没有关闭Statement。下年我们来看下prepareStatement方法的逻辑。

2)、prepareStatement(StatementHandler handler, Log statementLog)

private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
  Statement stmt;
  BoundSql boundSql = handler.getBoundSql();
  String sql = boundSql.getSql();
  if (hasStatementFor(sql)) {
    stmt = getStatement(sql);
    applyTransactionTimeout(stmt);
  } else {
    Connection connection = getConnection(statementLog);
    stmt = handler.prepare(connection, transaction.getTimeout());
    putStatement(sql, stmt);
  }
  handler.parameterize(stmt);
  return stmt;
}
private boolean hasStatementFor(String sql) {
  try {
    Statement statement = statementMap.get(sql);
    return statement != null && !statement.getConnection().isClosed();
  } catch (SQLException e) {
    return false;
  }
}

private Statement getStatement(String s) {
  return statementMap.get(s);
}

​ 这个就是prepareStatement对于Statement的获取逻辑。其首先是通过BoundSql来获取对应的sql语句,然后以其为key来看statementMap有没有,有的话就再来使用它,没有的话,就去创建Statement,然后放到statementMap中。

3)、doQuery(...)

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

​ 治理的query方法也主要是没有关闭Statement。

七、执行执行器的使用情况

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

​ 通过这个使用代码我们也明白了,就不再赘叙了。需要注意的是,这里也有调用interceptorChain.pluginAll(executor)。