第2篇-Executor执行器的三种形态
约 2645 字大约 9 分钟
2026-05-24
四、ReuseExecutor:复用 Statement#
4.1 ReuseExecutor 的特点#
ReuseExecutor 的特点是:它会缓存 Statement,下次执行相同的 SQL 时,可以复用 Statement。
// 类路径:org.apache.ibatis.executor.ReuseExecutor
public class ReuseExecutor extends BaseExecutor {
// 【核心】Statement 缓存(key:SQL 语句,value:Statement)
private final Map<String, Statement> statementMap = new HashMap<>();
public ReuseExecutor(Configuration configuration, Transaction transaction) {
super(configuration, transaction);
}
@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);
}
@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(重要!)
Statement stmt = prepareStatement(handler, ms.getStatementLog());
return handler.query(stmt, resultHandler);
}
// 【核心】创建或获取 Statement
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
BoundSql boundSql = handler.getBoundSql();
String sql = boundSql.getSql();
// 【核心】第一步:检查是否已经有缓存的 Statement
if (hasStatementFor(sql)) {
stmt = getStatement(sql); // 获取缓存的 Statement
applyTransactionTimeout(stmt); // 应用事务超时
} else {
// 【核心】第二步:如果没有缓存,就创建新的 Statement
Connection connection = getConnection(statementLog);
stmt = handler.prepare(connection, transaction.getTimeout());
putStatement(sql, stmt); // 放入缓存
}
handler.parameterize(stmt); // 设置参数
return stmt;
}
// 【核心】检查是否有缓存的 Statement
private boolean hasStatementFor(String sql) {
try {
Statement statement = statementMap.get(sql);
return statement != null && !statement.getConnection().isClosed();
} catch (SQLException e) {
return false;
}
}
// 【核心】获取缓存的 Statement
private Statement getStatement(String s) {
return statementMap.get(s);
}
// 【核心】缓存 Statement
private void putStatement(String sql, Statement stmt) {
statementMap.put(sql, stmt);
}
@Override
public List<BatchResult> doFlushStatements(boolean isRollback) {
for (Statement stmt : statementMap.values()) {
closeStatement(stmt); // 清除缓存的 Statement
}
statementMap.clear();
return Collections.emptyList();
}
}白话文解释:
- 检查是否有缓存的 Statement:
hasStatementFor(sql)会检查statementMap中是否已经有这个 SQL 对应的Statement。 - 如果有缓存:就直接获取它(
getStatement(sql)),不用重新创建。 - 如果没有缓存:就创建新的
Statement,并放入statementMap中(下次可以复用)。 - 设置参数:复用
Statement后,需要重新设置参数(因为参数可能变了)。
你可能会问:复用 Statement 有什么好处?因为创建 Statement 是有开销的(需要发送 PREPARE 命令给 MySQL 服务器)。如果复用 Statement,就不用再发送 PREPARE 命令了,能提升性能。
但是,复用 Statement 也有风险:如果 Statement 对应的连接已经关闭了,就会出错。所以,ReuseExecutor 在获取缓存的 Statement 时,会检查连接是否已关闭。
五、BatchExecutor:批处理执行器#
5.1 BatchExecutor 的特点#
BatchExecutor 的特点是:它会批量执行 SQL(比如批量插入、批量更新)。
// 类路径:org.apache.ibatis.executor.BatchExecutor
public class BatchExecutor extends BaseExecutor {
// 【核心】Statement 列表(用于批量执行)
private final List<Statement> statementList = new ArrayList<>();
// 【核心】批处理结果列表
private final List<BatchResult> batchResultList = new ArrayList<>();
// 【核心】当前的 SQL 和 MappedStatement(用于判断是否是同一个批处理)
private String currentSql;
private MappedStatement currentStatement;
public BatchExecutor(Configuration configuration, Transaction transaction) {
super(configuration, transaction);
}
@Override
public int doUpdate(MappedStatement ms, Object parameterObject) throws SQLException {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(this, ms, parameterObject,
RowBounds.DEFAULT, null, null);
BoundSql boundSql = handler.getBoundSql();
String sql = boundSql.getSql();
Statement stmt;
// 【核心】第一步:判断是否是同一个 SQL(重要!)
if (sql.equals(currentSql) && ms.equals(currentStatement)) {
// 【注意】如果是同一个 SQL,就获取最后一个 Statement
int last = statementList.size() - 1;
stmt = statementList.get(last);
applyTransactionTimeout(stmt);
// 【核心】第二步:添加批处理参数
handler.parameterize(stmt);
BatchResult batchResult = batchResultList.get(last);
batchResult.addParameterObject(parameterObject);
} else {
// 【注意】如果不是同一个 SQL,就创建新的 Statement
Connection connection = getConnection(ms.getStatementLog());
stmt = handler.prepare(connection, transaction.getTimeout());
handler.parameterize(stmt);
// 【核心】第三步:更新当前的 SQL 和 MappedStatement
currentSql = sql;
currentStatement = ms;
// 【核心】第四步:将 Statement 加入列表
statementList.add(stmt);
batchResultList.add(new BatchResult(ms, sql, parameterObject));
}
// 【核心】第五步:执行批处理(重要!)
handler.batch(stmt);
return BATCH_UPDATE_RETURN_VALUE; // 返回默认值(真正的结果在 flushStatements() 中获取)
}
@Override
public List<BatchResult> doFlushStatements(boolean isRollback) throws SQLException {
try {
// 【核心】第一步:如果回滚,就返回空列表
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());
// 【注意】如果是自增 ID,就处理它
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())) {
for (Object parameter : parameterObjects) {
keyGenerator.processAfter(this, ms, stmt, parameter);
}
}
} 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,
batchResultList, batchResult);
}
}
return batchResultList;
} finally {
// 【核心】第四步:关闭所有 Statement,并清除缓存
for (Statement stmt : statementList) {
closeStatement(stmt);
}
currentSql = null;
statementList.clear();
batchResultList.clear();
}
}
}白话文解释:
- 判断是否是同一个 SQL:如果当前执行的 SQL 和上次执行的 SQL 相同,就复用上次的
Statement;否则,就创建新的Statement。 - 添加批处理参数:调用
handler.batch(stmt)将参数添加到批处理中。 - 返回默认值:
doUpdate()方法返回BATCH_UPDATE_RETURN_VALUE(一个特殊的值,表示"批处理还没执行完")。 - 执行批处理:在
doFlushStatements()方法中,调用stmt.executeBatch()真正执行批处理。 - 获取批处理结果:
stmt.executeBatch()会返回一个数组,表示每个 SQL 的影响行数。
你可能会问:为什么要使用批处理?因为批处理可以减少网络开销。如果你要插入 1000 条数据,不用批处理就要发送 1000 次 SQL;而用批处理,可以只发送 1 次(MySQL 支持多语句执行)。
六、如何选择执行器类型?#
6.1 在 MyBatis 配置中指定#
在 mybatis-config.xml 中,你可以指定执行器类型:
<configuration>
<settings>
<!-- 【核心】指定执行器类型 -->
<setting name="defaultExecutorType" value="SIMPLE"/> <!-- 可选值:SIMPLE、REUSE、BATCH -->
</settings>
</configuration>或者在代码中指定:
// 【核心】创建 SqlSession 时指定执行器类型
SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);6.2 三种类型的对比#
| 执行器类型 | 特点 | 适用场景 |
|---|---|---|
SimpleExecutor | 每次创建新的 Statement | 默认值,适合大多数场景 |
ReuseExecutor | 复用 Statement | 适合执行大量相同的 SQL(比如批量查询) |
BatchExecutor | 批量执行 SQL | 适合批量插入、批量更新 |
七、埋下伏笔:StatementHandler 的"三大将"#
说到这里,你可能会问:StatementHandler 是什么?它又是如何创建 Statement 的?
这个问题,我们会在下一篇文章(系列三第3篇)中详细解答。这里先预告一下核心内容:
- StatementHandler 是 MyBatis 的"Statement 处理器",它负责创建
Statement、设置参数、执行 SQL、处理结果集。 - StatementHandler 有三种类型:
SimpleStatementHandler:处理普通的Statement。PreparedStatementHandler:处理PreparedStatement(防 SQL 注入)。CallableStatementHandler:处理存储过程。
- ParameterHandler:用于设置
PreparedStatement的参数。 - ResultSetHandler:用于处理结果集,将
ResultSet转换成 Java 对象。
下一篇文章的预告:《MyBatis 系列(三):StatementHandler 的"三大将"》
八、总结与思考#
今天我们一起深入了 MyBatis 的 Executor 执行器,了解了它的三种形态:SimpleExecutor、ReuseExecutor、BatchExecutor。
让我们用几个问题来检验一下今天的学习成果:
Executor 的作用是什么?
- 它是 MyBatis 的"SQL 执行指挥官",负责执行 INSERT、UPDATE、DELETE、SELECT 等 SQL。
- 它还负责控制事务的提交和回滚。
三种执行器的区别是什么?
SimpleExecutor:每次创建新的Statement,用完就关。ReuseExecutor:会缓存Statement,下次执行相同的 SQL 时,可以复用。BatchExecutor:批量执行 SQL,减少网络开销。
BaseExecutor 的一级缓存是如何工作的?
- 在执行查询前,先尝试从
localCache中获取结果。 - 如果找到了,直接返回;否则,就从数据库中查询,并将结果放入缓存。
- 在执行查询前,先尝试从
如何选择合适的执行器类型?
- 大多数场景:使用
SimpleExecutor(默认)。 - 执行大量相同的 SQL:使用
ReuseExecutor。 - 批量插入或更新:使用
BatchExecutor。
- 大多数场景:使用
九、扩展阅读#
【扩展点】自定义执行器#
你可以自定义执行器,实现特殊的业务逻辑。比如,你可以实现一个"监控执行器",用于统计每个 SQL 的执行时间。
// 【扩展点】自定义执行器示例
public class MyMonitoringExecutor extends BaseExecutor {
public MyMonitoringExecutor(Configuration configuration, Transaction transaction) {
super(configuration, transaction);
}
@Override
public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
long startTime = System.nanoTime();
try {
// 【注意】调用父类的 doUpdate() 方法
return super.doUpdate(ms, parameter);
} finally {
long endTime = System.nanoTime();
long executeTime = endTime - startTime;
System.out.println("SQL 执行时间:" + executeTime + "纳秒");
}
}
@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter,
RowBounds rowBounds, ResultHandler resultHandler,
BoundSql boundSql) throws SQLException {
long startTime = System.nanoTime();
try {
// 【注意】调用父类的 doQuery() 方法
return super.doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
long endTime = System.nanoTime();
long executeTime = endTime - startTime;
System.out.println("SQL 执行时间:" + executeTime + "纳秒");
}
}
// ... 其他方法(doFlushStatements、doQueryCursor 等)
}场景:你可以在自定义执行器中实现:
- SQL 执行时间监控(统计每个 SQL 的执行时间)。
- 慢查询日志(如果执行时间超过 1 秒,就记录日志)。
- SQL 审计(记录所有执行的 SQL)。
如何使用自定义执行器? 在创建 SqlSessionFactory 时,指定自定义执行器:
// 【扩展点】使用自定义执行器
@Bean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
factory.setDataSource(dataSource);
// 【注意】设置自定义执行器
factory.setExecutorType(ExecutorType.SIMPLE); // 这里只能设置类型,不能设置具体的执行器类
return factory.getObject();
}注意:MyBatis 不支持直接设置自定义执行器类。如果你要实现自定义执行器,需要通过插件(Interceptor)来拦截 Executor 的方法。
好了,今天的文章就到这里。下一篇,我们将深入 StatementHandler,看看它是如何创建 Statement、设置参数、执行 SQL、处理结果集的。敬请期待!
如果你觉得这篇文章对你有帮助,欢迎点赞、收藏、转发!你的支持是我继续创作的动力!
系列文章目录:
- 系列一:Spring 事务系列(已完成)
- 第1篇:@Transactional 注解的"前世今生"
- 第2篇:AOP 代理 —— 事务的"隐身衣"
- 第3篇:事务传播机制源码深度解析
- 第4篇:事务隔离级别的实现原理
- 第5篇:事务管理器的设计哲学
- 第6篇:事务资源的绑定与传递
- 第7篇:提交、回滚与异常规则
- 系列二:数据源系列(已完成)
- 第1篇:JDBC 规范与 DriverManager 的 legacy
- 第2篇:Druid 连接池的整体架构
- 第3篇:连接的借与还(getConnection 全流程)
- 第4篇:Druid 过滤链与监控统计
- 第5篇:MySQL 驱动的 JDBC 实现
- 第6篇:连接的生命周期与事务边界
- 系列三:MyBatis 系列(进行中)
- 第1篇:从 Mapper 接口到 SQL 执行的奇幻漂流(已完成)
- 第2篇:Executor 执行器的三种形态(本文)
- 第3篇:StatementHandler 的"三大将"
- 第4篇:参数处理与结果映射
- 第5篇:插件机制与责任链
- 系列四:总结系列(待开始)