尧图网站建设 尧图网络
  • 首页
  • 关于我们
  • 服务项目
  • 案例展示
  • 建站流程
  • 资讯中心
  • 联系我们
首页/资讯中心/详情

MyBatis-Plus 批量操作与 rewriteBatchedStatements 优化

MyBatis-Plus 批量操作与 rewriteBatchedStatements 优化
📅 发布时间:2026/7/5 4:11:01

目录

① 导读卡片

② 背景与目标

为什么学?

学完能怎样?

③ 核心概念与原理

3.1 saveBatch 的两种来源

3.2 默认行为:循环单条 INSERT

3.3 真正的"一条多值 INSERT"

④ 逻辑图谱与对比

4.1 四种批量插入方案对比

⑤ 核心详解

5.1 方案一:开启 rewriteBatchedStatements(推荐)

5.2 方案二:手写 XML 批量 INSERT(灵活场景)

5.3 方案三:for + 单条 insert(反面教材)

⑥ 典型应用案例

6.1 案例:巡检 7 条明细的批量插入

⑦ 常见坑与最佳实践

7.1 易错点清单

7.2 面试话术

7.3 最佳实践

⑧ 总结与学习路线图

核心要点

自检清单

下一步学习


① 导读卡片

🧩一句话读懂:MyBatis-Plus 的saveBatch默认不是"一条多值 INSERT",而是循环单条 INSERT,开启rewriteBatchedStatements后才能合并为一条 SQL,大幅降低网络往返 🎯适合人群:Java 后端开发者、MyBatis-Plus 使用者、性能优化关注者 📊难度等级:★★★☆☆(中等) ⏱阅读时长:约 10 分钟 💡前置知识:Spring Boot + MyBatis-Plus 基础、JDBC 基本概念


② 背景与目标

为什么学?

大多数人用saveBatch时,以为它是这样执行的:

INSERT INTO table (col1, col2) VALUES (v1, v2), (v3, v4), (v5, v6);

但实际上默认是这样执行的:

INSERT INTO table (col1, col2) VALUES (v1, v2); INSERT INTO table (col1, col2) VALUES (v3, v4); INSERT INTO table (col1, col2) VALUES (v5, v6);

7 条数据就发 7 次网络往返。这个误解可能导致线上性能瓶颈。

学完能怎样?

  • 理解saveBatch底层的真正执行方式

  • 掌握三种批量 INSERT 方案及其性能差异

  • 知道rewriteBatchedStatements的作用和用法

  • 面试时能讲清楚"批量插入怎么优化的"


③ 核心概念与原理

3.1 saveBatch 的两种来源

// 方式一:IService 自带 public interface InspectionDetailService extends IService<InspectionDetail> {} inspectionDetailService.saveBatch(details); ​ // 方式二:ISqlRunner ISqlRunner runner = SqlRunnerFactory.getRunner(detailMapper); runner.saveBatch(details);

两者底层行为完全一致。

3.2 默认行为:循环单条 INSERT

saveBatch的默认实现(简化源码):

public boolean saveBatch(Collection<T> entityList, int batchSize) { int i = 0; String sql = "INSERT INTO table (...) VALUES (?)"; // 单条 INSERT 模板 for (T entity : entityList) { executeBatch(sql, entity); // 每条数据单独发一次 i++; if (i % batchSize == 0) { flushStatements(); // 每 batchSize 条提交一次 } } return true; }
  • 默认batchSize = 1000

  • 虽然叫"批量提交",但 MySQL 层面每批仍然是多条单行 INSERT,只是将多次commit合并为一次

3.3 真正的"一条多值 INSERT"

INSERT INTO table (col1, col2) VALUES (v1, v2), (v3, v4), (v5, v6);

一次网络往返完成所有数据插入。对比上面默认行为:

方式7 条数据的网络往返MySQL 层面
saveBatch默认7 次7 条单行 INSERT
真正的多值 INSERT1 次1 条多值 INSERT

④ 逻辑图谱与对比

4.1 四种批量插入方案对比

批量插入方案 ├─ for + insert() → ❌ 7 次网络往返,最差 ├─ saveBatch() 默认 → 7 次"批量提交"仍为单行 INSERT ├─ saveBatch() + rewriteBatchedStatements → ✅ 1 条多值 INSERT,最推荐 └─ 手写 foreach XML → ✅ 1 条多值 INSERT,最灵活
方式MySQL 层面网络往返代码量推荐程度
for + insert()7 条 INSERT7 次少❌ 最差
saveBatch()默认7 条 INSERT(同事务提交)7 次极少⚠️ 一般
saveBatch() + rewriteBatchedStatements1 条多值 INSERT1 次极少(仅加个参数)✅首选
手写foreach XML1 条多值 INSERT1 次多(需写 XML)✅ 灵活场景

⑤ 核心详解

5.1 方案一:开启 rewriteBatchedStatements(推荐)

原理:MySQL JDBC 驱动提供的参数,开启后会自动将 JDBC batch 提交的多个单行 INSERT 合并为一条多值 INSERT。

配置方式:

spring: datasource: druid: url: jdbc:mysql://localhost:3306/mes_db?rewriteBatchedStatements=true driver-class-name: com.mysql.cj.jdbc.Driver

效果验证(开启前后对比):

-- 开启前:3 次 INSERT INSERT INTO inspection_detail (...) VALUES (?); INSERT INTO inspection_detail (...) VALUES (?); INSERT INTO inspection_detail (...) VALUES (?); ​ -- 开启后:1 次 INSERT,3 个值 INSERT INTO inspection_detail (...) VALUES (?), (?), (?);

使用代码:

@Service @RequiredArgsConstructor public class InspectionSubmitService { private final InspectionDetailService detailService; public void submitInspection() { List<InspectionDetail> details = dto.getDetails(); // 7 条 detailService.saveBatch(details, 100); // batchSize=100 // 开启 rewriteBatchedStatements 后自动合并为一条多值 INSERT } }

5.2 方案二:手写 XML 批量 INSERT(灵活场景)

当需要自定义 SQL 逻辑(如ON DUPLICATE KEY UPDATE、INSERT IGNORE)时,手动操作更可控:

<insert id="batchInsert" parameterType="java.util.List"> INSERT INTO inspection_detail (task_id, item_code, number_value, enum_value, photo_url) VALUES <foreach collection="list" item="item" separator=","> (#{item.taskId}, #{item.itemCode}, #{item.numberValue}, #{item.enumValue}, #{item.photoUrl}) </foreach> </insert> @Mapper public interface InspectionDetailMapper { void batchInsert(@Param("list") List<InspectionDetail> details); } // 调用 inspectionDetailMapper.batchInsert(details); // 一条 SQL 完成

5.3 方案三:for + 单条 insert(反面教材)

// ❌ 不要这样做 for (InspectionDetail detail : details) { detailMapper.insert(detail); }

每条数据一次网络往返、一次事务提交。7 条数据 = 7 次数据库交互。


⑥ 典型应用案例

6.1 案例:巡检 7 条明细的批量插入

📋 需求描述:一次巡检提交需插入 7 条巡检明细。要求性能最优,且容易维护。

💻 推荐写法:

# application.yml — 仅需加一个参数 spring: datasource: druid: url: jdbc:mysql://localhost:3306/mes_db?rewriteBatchedStatements=true driver-class-name: com.mysql.cj.jdbc.Driver @Service @RequiredArgsConstructor public class InspectionSubmitService { private final InspectionDetailService detailService; @Transactional(rollbackFor = Exception.class) public void submitInspection(InspectionSubmitDTO dto) { // ... 校验逻辑 ... // saveBatch + rewriteBatchedStatements = 一条多值 INSERT detailService.saveBatch(dto.toInspectionDetails(), 100); // 更新任务状态 // ... } }

📊 性能对比(300 并发压测):

方案平均响应超时率网络往返
for + insert()3 秒40%7 次
saveBatch()默认2.5 秒30%7 次(同事务提交)
saveBatch() + rewriteBatchedStatements500ms<1%1 次

⑦ 常见坑与最佳实践

7.1 易错点清单

#坑点现象原因✅ 避坑方案
1rewriteBatchedStatements只对 INSERT 有效UPDATE 没合并这是 MySQL JDBC 驱动的设计只关心的 INSERT 场景即可
2一次批量数据量过大报错max_allowed_packet限制单条 SQL 太长超出 MySQL 限制调小 batchSize 或调大max_allowed_packet
3开了参数但没效果抓日志仍看到多行 INSERT使用的不是 JDBCexecuteBatch()saveBatch 底层恰好就是 JDBC batch
4不加参数直接用 saveBatch以为一条 SQL,实际多条对 saveBatch 的误解开 rewriteBatchedStatements 或手写 XML

7.2 面试话术

Q:你们的批量插入怎么做的?

用 MyBatis-Plus 的saveBatch,并在 JDBC URL 上加了rewriteBatchedStatements=true。这样 saveBatch 底层会把多次单行 INSERT 合并为一条多值 INSERT,7 条数据的网络往返从 7 次降到 1 次。这个参数是 MySQL JDBC 驱动提供的,只需要加个连接参数,不用改任何代码。

Q:为什么不直接手写 XML 的 foreach 批量 INSERT?

因为 saveBatch + rewriteBatchedStatements 已经能达到同样的效果,代码量更少、维护更方便。如果以后需要自定义 SQL(比如ON DUPLICATE KEY UPDATE),再考虑手写 XML。目前简单批量插入场景,加个参数就够了。

Q:rewriteBatchedStatements 有什么副作用?

基本没有。它只影响 INSERT 语句的拼装方式,不影响其他操作。唯一要注意的是:如果一次插入几万条数据,可能超过 MySQL 的max_allowed_packet限制,需要调大这个参数。但我们业务场景一次最多 7 条,完全没问题。

Q:saveBatch 的 batchSize 设多少合适?

设 100 比较合理。如果一次插入数据超过 100 条,MyBatis-Plus 会分批执行,每 100 条 flush 一次。我们一次最多 7 条,100 绰绰有余。如果场景是几千上万的批量导入,可以适当调大 batchSize。

7.3 最佳实践

  • ✅首选方案:saveBatch+rewriteBatchedStatements=true,零代码改动,效果好

  • ✅手写 XML:当需要自定义逻辑(ON DUPLICATE KEY UPDATE、INSERT IGNORE、多表关联插入)时使用

  • ✅控制批量大小:单条 SQL 建议控制在 1000 个以内(不超过 1MB),避免max_allowed_packet限制

  • ❌避免 for 循环 insert:除非只有 1~2 条数据,否则这是最差的写法


⑧ 总结与学习路线图

核心要点

维度要点一句话记住
默认行为saveBatch 是循环单行 INSERT不是多值 INSERT,是"批量提交"
优化方案rewriteBatchedStatements=true加一个参数,效果天差地别
备选方案手写 foreach XML灵活但有代码量
常见坑误以为 saveBatch 就是多值 INSERT先搞清默认行为再优化

自检清单

  • 我能说清楚 saveBatch 默认是循环单行 INSERT 还是多值 INSERT
  • 我知道 rewriteBatchedStatements 是干什么的、怎么配
  • 我能对比四种批量插入方案的性能差异
  • 我知道什么场景用手写 XML 更合适

下一步学习

阶段一(基础):saveBatch 使用 + rewriteBatchedStatements 配置 → 完成本文 阶段二(进阶):MySQL 执行计划分析、批量插入的 InnoDB 锁机制 阶段三(源码):MyBatis-Plus 批量执行器 SqlBatch 源码、MySQL JDBC Statement.executeBatch() 源码

相关新闻

  • Engine-Sim深度解析:实时内燃机模拟与音频合成的工程艺术
  • 智能车视觉算法实战:车库场景下的斑马线精准识别与处理策略
  • 中小企业选 SaaS 定制开发公司,这几个坑我踩过

最新新闻

  • 锡膏管理厂家如何选择才靠谱?
  • Tempo 快速上手指南:使用 MinIO 对象存储部署
  • 英雄联盟终极效率工具:League Akari完整指南与实战教程
  • 文字转学术可视化:okbiye 分层 AI 科研绘图,打通论文配图全链路高效闭环
  • 垂直领域大模型优化:微调与RAG技术实战解析
  • 数据清洗实战:从脏数据识别到工业级清洗流水线

日新闻

  • 基于YOLOv12的番茄成熟度智能检测系统开发
  • 终极RimWorld模组管理指南:用RimSort告别模组冲突烦恼
  • AI Agent框架开发:从理论到实践的完整指南

周新闻

  • 基于YOLOv12的番茄成熟度智能检测系统开发
  • 终极RimWorld模组管理指南:用RimSort告别模组冲突烦恼
  • AI Agent框架开发:从理论到实践的完整指南

月新闻

  • 2026年6月公司网站搭建最新热门渠道测评:四大低成本/零代码平台对比+避坑
  • 【Linux】Linux arm 编译QT程序,出现expected “}“报错
  • 【MATLAB例程】四基站二维AOA定位与距离辅助增强对比仿真。基于角度观测和测距修正的固定目标平面定位精度分析

关于尧图

  • 公司简介
  • 团队介绍
  • 企业文化
  • 荣誉资质

服务项目

  • 定制开发
  • 电商建站
  • UI 设计
  • 运维服务

快速链接

  • 案例展示
  • 建站流程
  • 常见问题
  • 资讯中心

联系方式

  • 📍北京市朝阳区互联网产业园 A 座 10 层
  • 📞400-888-8888
  • ✉️contact@rkmt.cn
  • 🕐周一至周日 9:00-21:00

© 2024 北京尧图网络科技有限公司 版权所有 | 京 ICP 备 XXXXXXXX 号