Mybatis 都有哪些 Executor 执行器?
一则或许对你有用的小广告
欢迎 加入小哈的星球 ,你将获得: 专属的项目实战(已更新的所有项目都能学习) / 1v1 提问 / Java 学习路线 / 学习打卡 / 每月赠书 / 社群讨论
- 新开坑项目: 《Spring AI 项目实战(问答机器人、RAG 增强检索、联网搜索)》 正在持续爆肝中,基于
Spring AI + Spring Boot3.x + JDK 21..., 点击查看; - 《从零手撸:仿小红书(微服务架构)》 已完结,基于
Spring Cloud Alibaba + Spring Boot3.x + JDK 17..., 点击查看项目介绍; 演示链接: http://116.62.199.48:7070/; - 《从零手撸:前后端分离博客项目(全栈开发)》 2 期已完结,演示链接: http://116.62.199.48/
面试考察点
-
框架原理理解:面试官不仅仅是想知道有几种 Executor,更是想考察你是否理解 MyBatis 的 SQL 执行层设计——
Executor作为核心调度者,如何与StatementHandler、缓存、事务协作。 -
差异化认知:看你能否说清楚三种 Executor 的行为差异(Statement 管理、批量执行),以及各自适用的业务场景。
-
生产实践意识:考察你是否知道如何选择合适的 Executor,以及
BatchExecutor在批量场景下的正确使用方式。
核心答案
MyBatis 提供了 3 种内置的 Executor 执行器(都实现了 Executor 接口),另外还有一个装饰器 CachingExecutor:
| Executor 类型 | 行为特点 | 适用场景 | Statement 管理 |
|---|---|---|---|
SimpleExecutor | 每次 SQL 都创建新的 Statement,执行完关闭 | 通用场景(默认) | 一条一创建一关闭 |
ReuseExecutor | 缓存 Statement,相同 SQL 复用 | 短时间内重复执行相同 SQL | 缓存复用 |
BatchExecutor | 通过 addBatch() 缓存 SQL,统一 executeBatch() | 批量插入/更新 | 批量提交 |
一句话结论:默认使用 SimpleExecutor,批量操作用 BatchExecutor,ReuseExecutor 少见。无论哪种,都可以被 CachingExecutor 装饰来支持二级缓存。
深度解析
一、三种 Executor 的执行差异
三种 Executor 的核心差异在于 Statement 的生命周期管理和 SQL 执行策略:
-
SimpleExecutor:最简单的实现,每次执行 SQL 都创建一个新的PreparedStatement,执行完毕后立即关闭。好处是简单、不会有资源泄漏;缺点是如果同一条 SQL 在一次请求中被多次执行(比如循环中调用),每次都要重新创建Statement,有额外开销。 -
ReuseExecutor:维护一个Map<String, Statement>缓存,以 SQL 字符串为 key。执行 SQL 时先查缓存,如果有就复用,没有才创建。Statement不在每次执行后关闭,而是在SqlSession关闭时统一关闭。适合短时间内在一个SqlSession中多次执行相同 SQL 的场景。 -
BatchExecutor:专门为批量操作设计。执行 SQL 时不立即发送到数据库,而是通过PreparedStatement.addBatch()将 SQL 添加到 JDBC 的批处理缓冲区中,调用commit()或flushStatements()时才通过executeBatch()一次性提交。适合需要一次性插入/更新大量数据的场景。
二、配置方式
方式一:全局配置
在 mybatis-config.xml 中指定默认的 Executor 类型:
<settings>
<!-- 可选值:SIMPLE(默认)、REUSE、BATCH -->
<setting name="defaultExecutorType" value="SIMPLE"/>
</settings>
方式二:创建 SqlSession 时指定
// 指定使用 BatchExecutor
SqlSession batchSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
// 指定使用 ReuseExecutor
SqlSession reuseSession = sqlSessionFactory.openSession(ExecutorType.REUSE);
// 默认 SimpleExecutor
SqlSession simpleSession = sqlSessionFactory.openSession();
方式三:Spring 整合中配置
@Configuration
public class MyBatisConfig {
// 全局配置:在 SqlSessionFactoryBean 中指定
@Bean
public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource) {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
org.apache.ibatis.session.Configuration config =
new org.apache.ibatis.session.Configuration();
config.setDefaultExecutorType(ExecutorType.REUSE);
bean.setConfiguration(config);
return bean;
}
// 单独创建批量 SqlSessionTemplate
@Bean("batchSqlSessionTemplate")
public SqlSessionTemplate batchSqlSessionTemplate(
SqlSessionFactory sqlSessionFactory) {
return new SqlSessionTemplate(sqlSessionFactory, ExecutorType.BATCH);
}
}
三、CachingExecutor——二级缓存的装饰器
除了三种基本 Executor,MyBatis 还有一个 CachingExecutor,它不是一种独立的 Executor,而是一个装饰器:
CachingExecutor 是装饰器模式的典型应用:
- 当二级缓存开启(
cacheEnabled = true,默认开启)时,MyBatis 会用CachingExecutor包装实际的 Executor(Simple/Reuse/Batch)。 CachingExecutor在查询时先检查二级缓存,命中则直接返回;未命中则委托给底层 Executor 执行,执行完将结果写入二级缓存。- 更新操作时,
CachingExecutor会清空对应的二级缓存。
这就是为什么查询的完整顺序是 "二级缓存 → 一级缓存 → 数据库"。
四、三种 Executor 的源码差异
三种 Executor 的核心区别在 doUpdate() 方法中(简化版):
// SimpleExecutor:每次都创建新 Statement
public int doUpdate(MappedStatement ms, Object parameter) {
Statement stmt = null;
try {
stmt = prepareStatement(handler); // 每次创建新的
return handler.update(stmt); // 立即执行
} finally {
closeStatement(stmt); // 立即关闭
}
}
// ReuseExecutor:缓存 Statement
public int doUpdate(MappedStatement ms, Object parameter) {
Statement stmt = prepareStatement(handler); // 先查缓存,没有才创建
return handler.update(stmt); // 立即执行(不关闭)
// Statement 保存在 Map<String, Statement> 中
}
// BatchExecutor:addBatch 缓存
public int doUpdate(MappedStatement ms, Object parameter) {
Statement stmt = prepareStatement(handler);
handler.batch(stmt); // 调用 addBatch(),不执行!
return BATCH_UPDATE_RETURN_VALUE; // 返回常量,不是实际结果
}
关键源码差异总结:
SimpleExecutor的doUpdate()每次都创建Statement、执行 SQL、然后关闭。最简单直接。ReuseExecutor的doUpdate()复用缓存的Statement,执行 SQL 但不关闭,等SqlSession关闭时统一清理。BatchExecutor的doUpdate()只调用addBatch(),不执行 SQL,返回一个常量值。真正的执行要等到doFlushStatements()被调用时才发生。
五、生产环境选择建议
| 场景 | 推荐 Executor | 原因 |
|---|---|---|
| 通用 CRUD | SimpleExecutor(默认) | 简单可靠,无资源泄漏风险 |
| 批量插入/更新 | BatchExecutor | addBatch() + executeBatch() 减少网络往返 |
| 短时间内重复执行相同 SQL | ReuseExecutor | 减少 Statement 创建开销 |
| 需要二级缓存 | 任意 + CachingExecutor 装饰 | 二级缓存是装饰器,和具体 Executor 无关 |
六、常见误区
- 误区一:"
BatchExecutor比SimpleExecutor快很多"- 不一定。
BatchExecutor的优势在于减少网络往返次数(多条 SQL 一次提交),但如果只执行单条 SQL,性能和SimpleExecutor一样。而且BatchExecutor不支持SELECT,所以只适合批量写操作。
- 不一定。
- 误区二:"开启了
ReuseExecutor就能大幅提升性能"- 提升有限。
Statement的创建开销在整个 SQL 执行耗时中占比很小(网络 IO 和 SQL 解析才是大头)。实际项目中很少用ReuseExecutor,SimpleExecutor就够用了。
- 提升有限。
- 误区三:"
BatchExecutor的insert()方法会立即返回自增主键"- 不会。
BatchExecutor的doUpdate()只调用addBatch(),不执行 SQL,所以无法立即获取自增主键。必须调用flushStatements()后才能拿到。
- 不会。
面试高频追问
- 追问一:为什么默认使用
SimpleExecutor而不是ReuseExecutor?- 因为
SimpleExecutor最安全——每次用完就关闭,不会出现Statement泄漏问题。ReuseExecutor缓存的Statement如果忘记关闭SqlSession,会导致数据库连接泄漏。安全优先于微小的性能提升。
- 因为
- 追问二:
BatchExecutor和<foreach>批量插入有什么区别?<foreach>是在 SQL 层面拼接多值 INSERT(一条 SQL 搞定),BatchExecutor是在 JDBC 层面批量提交多条单值 INSERT。性能上<foreach>通常更快,但有 SQL 长度限制;BatchExecutor没有长度限制,配合rewriteBatchedStatements=true后性能接近。
- 追问三:
CachingExecutor和具体的 Executor 是什么关系?- 装饰器模式。
CachingExecutor持有一个delegate(实际 Executor),在 delegate 的基础上增加了二级缓存功能。查询时先走CachingExecutor的二级缓存,未命中再委托给 delegate 的一级缓存和数据库。
- 装饰器模式。
常见面试变体
- 变体一:"MyBatis 的
Executor接口有哪些实现类?" - 变体二:"
SimpleExecutor和BatchExecutor的区别?" - 变体三:"MyBatis 二级缓存的
CachingExecutor是怎么实现的?"
记忆口诀
Simple 每条创建关,Reuse 缓存复用省开销,Batch 缓冲统一交,Caching 装饰加二级缓存。
总结
MyBatis 有 3 种内置 Executor:SimpleExecutor(默认,每次创建关闭 Statement)、ReuseExecutor(缓存复用 Statement)、BatchExecutor(批量 addBatch 提交)。另有 CachingExecutor 装饰器为任意 Executor 添加二级缓存能力。生产环境默认用 SimpleExecutor,批量操作切换为 BatchExecutor。