Mybatis 都有哪些 Executor 执行器?它们之间的区别是什么?

基于:Mybatis 都有哪些 Executor 执行器?它们之间的区别是什么?

在 MyBatis 中,Executor 是一个关键的接口,负责执行映射的 SQL 语句。MyBatis 提供了四种类型的 Executor,每种 Executor 在执行 SQL 和处理事务方面都有不同的特点。本文将详细介绍这四种 Executor 及其区别。

1. 简介

在 MyBatis 中,Executor 是负责执行数据库操作的核心组件。它抽象了具体的执行过程,使得开发者无需直接处理 JDBC 代码。MyBatis 提供了四种主要的 Executor 实现:

  1. SimpleExecutor —— 简单执行器

  2. ReuseExecutor —— 语句复用执行器

  3. BatchExecutor —— 批处理执行器

  4. CachingExecutor —— 缓存增强执行器

每种 Executor 都有其特定的用途和使用场景。理解它们的区别有助于我们在实际开发中选择合适的执行器,提高应用的性能和效率。

2. SimpleExecutor

简介

SimpleExecutor 是 MyBatis 最基础的执行器。每次执行 SQL 操作时,SimpleExecutor 都会创建一个新的 Statement 对象。这种方式简单直接,但在频繁执行相同 SQL 的场景下,性能可能不太理想,因为每次都要重新创建 Statement 对象。

工作原理

SimpleExecutor 的工作流程如下:

  1. 打开数据库连接。

  2. 创建新的 Statement 对象。

  3. 执行 SQL 语句。

  4. 处理结果集(如果有)。

  5. 关闭 Statement 对象。

代码片段

public class SimpleExecutor extends BaseExecutor {
  @Override
  public int doUpdate(MappedStatement ms, Object parameter) {
    Statement stmt = null;
    try {
      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);
    } finally {
      // 关闭 Statement
      closeStatement(stmt);
    }
  }

  private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    // 获取数据库连接
    Connection connection = getConnection(statementLog);
    // 创建 Statement
    stmt = handler.prepare(connection, transaction.getTimeout());
    handler.parameterize(stmt);
    return stmt;
  }
}

使用场景

SimpleExecutor 适用于简单的、不会频繁重复执行相同 SQL 语句的场景。在这种情况下,SimpleExecutor 的性能损耗可以忽略不计,而且它的实现也比较简单,不需要考虑 Statement 对象的复用问题。

3. ReuseExecutor

简介

ReuseExecutor 在执行 SQL 操作时会复用 Statement 对象。每次执行 SQL 时,ReuseExecutor 会检查是否已经存在可用的 Statement 对象,如果存在则直接复用,否则创建新的 Statement 对象。这种方式可以减少 Statement 对象的创建次数,提高性能。

工作原理

ReuseExecutor 的工作流程如下:

  1. 打开数据库连接。

  2. 检查是否存在可复用的 Statement 对象。

    1. 如果存在,直接复用。
    2. 如果不存在,创建新的 Statement 对象并保存。
  3. 执行 SQL 语句。

  4. 处理结果集(如果有)。

  5. 不关闭 Statement 对象,以便下次复用。

代码片段

public class ReuseExecutor extends BaseExecutor {
  private final Map<String, Statement> statementMap = new HashMap<>();

  @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
    Statement stmt = prepareStatement(handler, ms.getStatementLog());
    return handler.update(stmt);
  }

  private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    BoundSql boundSql = handler.getBoundSql();
    String sql = boundSql.getSql();
    if (hasStatementFor(sql)) {
      // 有可复用的 Statement,获取后返回
      stmt = getStatement(sql);
      applyTransactionTimeout(stmt);
    } else {
      Connection connection = getConnection(statementLog);
      // 有可复用的 Statement,创建新的并添加到 statementMap 中
      stmt = handler.prepare(connection, transaction.getTimeout());
      putStatement(sql, stmt);
    }
    handler.parameterize(stmt);
    return stmt;
  }
}

使用场景

ReuseExecutor 适用于频繁执行相同 SQL 语句的场景。例如,在应用中多次查询同一张表的数据,使用 ReuseExecutor 可以显著减少 Statement 对象的创建和销毁次数,从而提高性能。

4. BatchExecutor

简介

BatchExecutor 通过批量执行 SQL 语句来提高性能。它将多条 SQL 语句放入一个批处理中,统一执行。这种方式可以减少数据库的交互次数,从而提高执行效率。

工作原理

BatchExecutor 的工作流程如下:

  1. 打开数据库连接。

  2. 创建新的 Statement 对象。

  3. 将多条 SQL 语句添加到批处理。

  4. 执行批处理中的所有 SQL 语句。

  5. 处理结果集(如果有)。

  6. 关闭 Statement 对象。

代码片段

public class BatchExecutor extends BaseExecutor {
  private final List<Statement> statementList = new ArrayList<Statement>();
  private final List<BatchResult> batchResults = new ArrayList<>();
    
  @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.parameterize(stmt);
    // 添加 SQL 语句到批处理中
    handler.batch(stmt);
    return BATCH_UPDATE_RETURN_VALUE;
  }

  @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.<E>query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }
    
  @Override
  public List<BatchResult> doFlushStatements(boolean isRollback) throws SQLException {
    try {
      List<BatchResult> results = new ArrayList<BatchResult>();
      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();
          if (Jdbc3KeyGenerator.class.equals(keyGenerator.getClass())) {
            Jdbc3KeyGenerator jdbc3KeyGenerator = (Jdbc3KeyGenerator) keyGenerator;
            jdbc3KeyGenerator.processBatch(ms, stmt, parameterObjects);
          } else if (!NoKeyGenerator.class.equals(keyGenerator.getClass())) { //issue #141
            for (Object parameter : parameterObjects) {
              keyGenerator.processAfter(this, ms, stmt, parameter);
            }
          }
          // Close statement to close cursor #1109
          closeStatement(stmt);
        } catch (BatchUpdateException e) {
          StringBuilder message = new StringBuilder();
          message.append(batchResult.getMappedStatement().getId())
              .append(" (batch index #")
              .append(i + 1)
              .append(")")
              .append(" failed.");
          if (i > 0) {
            message.append(" ")
                .append(i)
                .append(" prior sub executor(s) completed successfully, but will be rolled back.");
          }
          throw new BatchExecutorException(message.toString(), e, results, batchResult);
        }
        // 添加结果
        results.add(batchResult);
      }
      return results;
    } finally {
      for (Statement stmt : statementList) {
        // 关闭 Statement
        closeStatement(stmt);
      }
      currentSql = null;
      statementList.clear();
      batchResultList.clear();
    }
  }
}

使用场景

CachingExecutor 使用装饰模式对当前执行器做增强,在当前执行器的基础上添加了二级缓存,启用二级缓存时自动使用。

5. CachingExecutor

简介

BatchExecutor 通过批量执行 SQL 语句来提高性能。它将多条 SQL 语句放入一个批处理中,统一执行。这种方式可以减少数据库的交互次数,从而提高执行效率。

工作原理

CachingExecutor 的工作流程如下:

  1. 查询时创建缓存键。
  2. 检查是否有缓存,有则直接返回缓存数据。
  3. 否则执行查询,并缓存结果。

代码片段

public class CachingExecutor implements Executor {
  private final Executor delegate;

  @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.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
          tcm.putObject(cache, key, list); // issue #578 and #116
        }
        // 返回数据
        return list;
      }
    }
    // 缓存数据不存在,执行查询
    return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }
}

使用场景

CachingExecutor 在启用二级缓存时自动使用。

6. Executor 的选择

在实际应用中,如何选择合适的 Executor 是一个需要根据具体情况进行权衡的问题。以下是一些选择建议(CachingExecutor 受其他 Executor 的影响,这里不考虑):

  1. SimpleExecutor:适用于简单的、不会频繁重复执行相同 SQL 语句的场景。它的实现简单,适合初学者和小型项目。

  2. ReuseExecutor:适用于频繁执行相同 SQL 语句的场景。它通过复用 Statement 对象来提高性能,适合中小型项目。

  3. BatchExecutor:适用于批量操作的场景。它通过批量执行 SQL 语句来提高性能,适合需要处理大量数据的项目。

7. 性能比较

为了更好地理解三种 Executor 的性能差异,我们可以通过一个简单的示例进行比较(CachingExecutor 受其他 Executor 的影响,这里不考虑)。假设我们有一个批量插入数据的操作,需要插入 10000 条记录。

SimpleExecutor

使用 SimpleExecutor,每插入一条记录都会创建和关闭一个 Statement 对象。这种方式在执行 10000 条插入操作时,需要创建和销毁 10000 个 Statement 对象,性能较低。

ReuseExecutor

使用 ReuseExecutor,每插入一条记录时,会复用已有的 Statement 对象。这种方式在执行 10000 条插入操作时,只需要创建一个 Statement 对象,显著减少了创建和销毁 Statement 对象的次数,性能较高。

BatchExecutor

使用 BatchExecutor,将 10000 条插入操作放入一个批处理中,统一执行。这种方式只需要与数据库交互一次,极大地提高了性能。

8. 总结

在 MyBatis 中,Executor 是一个关键的组件,负责执行映射的 SQL 语句。MyBatis 提供了四种类型的 Executor:SimpleExecutor、ReuseExecutor、BatchExecutor 和 CachingExecutor。每种 Executor 在执行 SQL 和处理事务方面都有不同的特点和适用场景。了解它们的区别和使用场景,有助于我们在实际开发中选择合适的执行器,提高应用的性能和效率。

总的来说:

  • SimpleExecutor:适用于简单的、不会频繁重复执行相同 SQL 语句的场景。

  • ReuseExecutor:适用于频繁执行相同 SQL 语句的场景,通过复用 Statement 对象来提高性能。

  • BatchExecutor:适用于批量操作的场景,通过批量执行 SQL 语句来提高性能。

  • CachingExecutor:适用于启用二级缓存的场景,通过缓存查询结果来提高性能。

在实际应用中,我们应根据具体情况选择合适的 Executor,以达到最佳的性能表现。通过合理选择和使用 Executor,我们可以充分发挥 MyBatis 的优势,提高应用的开发效率和运行性能。

posted @ 2025-11-05 23:04  Higurashi-kagome  阅读(6)  评论(0)    收藏  举报