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/

面试考察点

  1. 框架原理理解:面试官不仅仅是想知道有几种 Executor,更是想考察你是否理解 MyBatis 的 SQL 执行层设计——Executor 作为核心调度者,如何与 StatementHandler、缓存、事务协作。

  2. 差异化认知:看你能否说清楚三种 Executor 的行为差异(Statement 管理、批量执行),以及各自适用的业务场景。

  3. 生产实践意识:考察你是否知道如何选择合适的 Executor,以及 BatchExecutor 在批量场景下的正确使用方式。

核心答案

MyBatis 提供了 3 种内置的 Executor 执行器(都实现了 Executor 接口),另外还有一个装饰器 CachingExecutor

Executor 类型行为特点适用场景Statement 管理
SimpleExecutor每次 SQL 都创建新的 Statement,执行完关闭通用场景(默认一条一创建一关闭
ReuseExecutor缓存 Statement,相同 SQL 复用短时间内重复执行相同 SQL缓存复用
BatchExecutor通过 addBatch() 缓存 SQL,统一 executeBatch()批量插入/更新批量提交

一句话结论:默认使用 SimpleExecutor,批量操作用 BatchExecutorReuseExecutor 少见。无论哪种,都可以被 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;  // 返回常量,不是实际结果
}

关键源码差异总结:

  • SimpleExecutordoUpdate() 每次都创建 Statement、执行 SQL、然后关闭。最简单直接。
  • ReuseExecutordoUpdate() 复用缓存的 Statement,执行 SQL 但不关闭,等 SqlSession 关闭时统一清理。
  • BatchExecutordoUpdate() 只调用 addBatch()不执行 SQL,返回一个常量值。真正的执行要等到 doFlushStatements() 被调用时才发生。

五、生产环境选择建议

场景推荐 Executor原因
通用 CRUDSimpleExecutor(默认)简单可靠,无资源泄漏风险
批量插入/更新BatchExecutoraddBatch() + executeBatch() 减少网络往返
短时间内重复执行相同 SQLReuseExecutor减少 Statement 创建开销
需要二级缓存任意 + CachingExecutor 装饰二级缓存是装饰器,和具体 Executor 无关

六、常见误区

  1. 误区一:"BatchExecutorSimpleExecutor 快很多"
    • 不一定。BatchExecutor 的优势在于减少网络往返次数(多条 SQL 一次提交),但如果只执行单条 SQL,性能和 SimpleExecutor 一样。而且 BatchExecutor 不支持 SELECT,所以只适合批量写操作。
  2. 误区二:"开启了 ReuseExecutor 就能大幅提升性能"
    • 提升有限。Statement 的创建开销在整个 SQL 执行耗时中占比很小(网络 IO 和 SQL 解析才是大头)。实际项目中很少用 ReuseExecutorSimpleExecutor 就够用了。
  3. 误区三:"BatchExecutorinsert() 方法会立即返回自增主键"
    • 不会。BatchExecutordoUpdate() 只调用 addBatch(),不执行 SQL,所以无法立即获取自增主键。必须调用 flushStatements() 后才能拿到。

面试高频追问

  1. 追问一:为什么默认使用 SimpleExecutor 而不是 ReuseExecutor
    • 因为 SimpleExecutor 最安全——每次用完就关闭,不会出现 Statement 泄漏问题。ReuseExecutor 缓存的 Statement 如果忘记关闭 SqlSession,会导致数据库连接泄漏。安全优先于微小的性能提升。
  2. 追问二BatchExecutor<foreach> 批量插入有什么区别?
    • <foreach> 是在 SQL 层面拼接多值 INSERT(一条 SQL 搞定),BatchExecutor 是在 JDBC 层面批量提交多条单值 INSERT。性能上 <foreach> 通常更快,但有 SQL 长度限制;BatchExecutor 没有长度限制,配合 rewriteBatchedStatements=true 后性能接近。
  3. 追问三CachingExecutor 和具体的 Executor 是什么关系?
    • 装饰器模式。CachingExecutor 持有一个 delegate(实际 Executor),在 delegate 的基础上增加了二级缓存功能。查询时先走 CachingExecutor 的二级缓存,未命中再委托给 delegate 的一级缓存和数据库。

常见面试变体

  • 变体一:"MyBatis 的 Executor 接口有哪些实现类?"
  • 变体二:"SimpleExecutorBatchExecutor 的区别?"
  • 变体三:"MyBatis 二级缓存的 CachingExecutor 是怎么实现的?"

记忆口诀

Simple 每条创建关,Reuse 缓存复用省开销,Batch 缓冲统一交,Caching 装饰加二级缓存。

总结

MyBatis 有 3 种内置 Executor:SimpleExecutor(默认,每次创建关闭 Statement)、ReuseExecutor(缓存复用 Statement)、BatchExecutor(批量 addBatch 提交)。另有 CachingExecutor 装饰器为任意 Executor 添加二级缓存能力。生产环境默认用 SimpleExecutor,批量操作切换为 BatchExecutor