MyBatis 如何执行批量操作?
2026年01月07日
一则或许对你有用的小广告
欢迎 加入小哈的星球 ,你将获得: 专属的项目实战(已更新的所有项目都能学习) / 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/
面试考察点
面试官提出这个问题,通常旨在考察以下几个维度:
- 实际使用经验:是否在实际项目中遇到过需要批量操作的场景,并真正使用过 MyBatis 的批量功能。
- 对 MyBatis 执行器原理的理解:是否了解 MyBatis 核心组件
Executor的工作原理,以及批量模式 (BatchExecutor) 与普通模式 (SimpleExecutor) 的本质区别。 - 性能优化意识:能否清晰阐述不同批量实现方式背后的性能考量(如减少网络往返、数据库连接开销、SQL 预编译等),而不仅仅是停留在 API 调用层面。
- 资源管理与事务意识:是否关注批量操作中的数据库连接持有时间、事务边界控制,以及可能带来的内存溢出或连接耗尽风险。
- 最佳实践与踩坑经验:能否给出生产环境中正确的使用方式、参数配置以及常见的注意事项,这能直接反映候选人的实战经验。
核心答案
MyBatis 执行批量操作主要有三种核心方式,其效率和适用场景各不相同:
- 使用
BatchExecutor执行器:在获取SqlSession时指定执行器类型为ExecutorType.BATCH。这是最高效的批量操作方式,因为它会缓存预编译的PreparedStatement,对同一 SQL 模板进行批量参数填充后一次性提交。 - 使用
<foreach>标签拼接动态 SQL:在 Mapper XML 中,将多个数据值通过foreach循环拼接成一条包含多个VALUES或OR条件的巨型 SQL 语句。这种方式一次性网络交互,但SQL长度可能超限,且数据库解析压力大。 - 在循环中使用
SqlSession的insert/update/delete方法:这是效率最低的方式,因为每次循环都是一次独立的数据库会话操作(除非配合BatchExecutor),会产生大量网络往返和事务开销。
结论:对于大数据量、高性能要求的批量插入/更新,首选 BatchExecutor。对于数据量不大(如几十到几百条)且数据库支持多值插入语法的情况,可以使用 <foreach> 作为简便替代。
深度解析
原理/机制
-
BatchExecutor工作原理:- 当执行器类型设置为
BATCH后,MyBatis 会使用BatchExecutor实例。 - 对于相同的 SQL 语句(
MappedStatement),BatchExecutor会缓存其对应的PreparedStatement对象。 - 每次调用
sqlSession.insert(“insertUser”, user),并不会立即执行,而是将参数添加到批处理列表中。 - 当调用
sqlSession.commit()、sqlSession.flushStatements()或执行一个不同的 SQL 语句时,它会将缓存的所有参数一次性发送到数据库执行,本质上是调用了 JDBC 的PreparedStatement.addBatch()和executeBatch()。 - 这种方式最大程度复用了预编译语句和数据库连接,是 JDBC 层面最高效的批量操作。
- 当执行器类型设置为
-
<foreach>动态 SQL 原理:- 这是一种 “逻辑批量”,在 MyBatis 动态 SQL 引擎层面,将集合数据拼接成一条长的 SQL 字符串。例如,生成
INSERT INTO table (a,b) VALUES (1,2), (3,4), (5,6)。 - 对于数据库而言,这只是一条普通的 INSERT 语句,只需要一次编译、一次网络传输、一次执行。但其长度受数据库
max_allowed_packet等参数限制。
- 这是一种 “逻辑批量”,在 MyBatis 动态 SQL 引擎层面,将集合数据拼接成一条长的 SQL 字符串。例如,生成
代码示例
方式一:使用 BatchExecutor(推荐)
// 1. 获取批量模式的 SqlSession
try (SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH)) {
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> userList = generateLargeUserList();
for (User user : userList) {
mapper.insertUser(user); // 这里只是将操作加入批处理缓存,并未执行
// 每积累一定数量(例如1000条),可以手动刷写一次,避免内存占用过大
// if (i % 1000 == 0) {
// sqlSession.flushStatements();
// }
}
// 2. 最终一次性提交,执行所有批处理操作
sqlSession.commit(); // commit 会自动触发 flushStatements
}
方式二:使用 <foreach> 动态 SQL
首先,在 Mapper XML 中定义:
<insert id="batchInsert" parameterType="java.util.List">
INSERT INTO user (name, age, email) VALUES
<foreach collection="list" item="user" separator=",">
(#{user.name}, #{user.age}, #{user.email})
</foreach>
</insert>
然后,在 Java 代码中调用:
// 获取普通模式的 SqlSession 即可
try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> userList = generateUserList();
mapper.batchInsert(userList); // 一次性执行一条巨型SQL
sqlSession.commit();
}
对比分析与最佳实践
| 特性 | BatchExecutor | <foreach> 动态SQL |
|---|---|---|
| 数据库交互次数 | 1次(批量执行) | 1次(单条SQL) |
| SQL预编译 | 复用同一个 PreparedStatement | 每次都是新SQL,需要数据库重新解析 |
| 网络传输 | 批量参数,高效 | 单条长SQL,可能很大 |
| 内存占用 | 在客户端缓存参数和语句 | 在客户端拼接长字符串 |
| 主要限制 | 需要手动控制刷写和提交 | SQL长度受数据库配置限制 |
| 适用场景 | 大数据量的插入、更新、删除 | 中小数据量(建议<1000),且数据库支持多值语法 |
最佳实践:
- 始终在事务中操作:批量操作必须放在事务中,要么全部成功,要么全部失败。使用
try-with-resources或确保finally中关闭SqlSession。 - 合理控制批处理大小:使用
BatchExecutor时,每处理一定数量(如 1000、2000)后,手动调用sqlSession.flushStatements()来清空批处理缓存,避免OutOfMemoryError和PreparedStatement占用内存过大。但注意,频繁刷写会降低批处理效率,需根据数据量和内存权衡。 - 连接池配置:确保数据库连接池(如 HikariCP)的
maximumPoolSize足够,因为BatchExecutor会长时间占用一个连接直到提交。 <foreach>的batchSize参数:MyBatis 3.5.0+ 提供了@Options(flushCache = Options.FlushCachePolicy.TRUE)等更精细的控制,但核心还是依赖上述两种模式。
常见误区
- 误区一:以为在循环中调用
Mapper方法就是批量操作。实际上,在默认的SimpleExecutor下,每次循环都是一次独立的 JDBC 执行,性能极差。 - 误区二:认为
<foreach>在任何情况下都比BatchExecutor快。对于大数据量,<foreach>生成的 SQL 可能非常庞大,导致数据库解析耗时剧增甚至报错,而BatchExecutor的稳定性和可管理性更好。
总结
执行 MyBatis 批量操作,追求极致性能应选用 BatchExecutor 并注意控制批处理大小和事务;对于轻量级批量需求,使用 <foreach> 生成多值 SQL 则更为便捷。理解其底层分别基于 JDBC 批处理和 SQL 拼接的原理,是正确选型和优化的关键。