Java JDBC 批量写入的事务和参数绑定
Java JDBC 批量写入的关键不是简单地把多条 SQL 放进循环,而是把三个边界同时设计清楚:同一条 PreparedStatement 如何绑定多组参数、多少行组成一个 batch,以及这一批数据何时提交或回滚。最小可靠做法是关闭自动提交,复用预编译语句,达到窗口大小后调用 executeBatch(),成功就 commit(),失败就 rollback()。
官方参考:https://docs.oracle.com/en/java/javase/26/docs/api/java.sql/java/sql/PreparedStatement.html
批量更新接口:https://docs.oracle.com/en/java/javase/26/docs/api/java.sql/java/sql/Statement.html
如果一批写入必须作为一个业务单元,就把setAutoCommit(false)、executeBatch()和commit()放在同一个明确的事务窗口里;如果数据量很大,则用固定窗口分批提交,不要让整个文件导入永远占着一个事务。
先区分参数批量和事务批量
PreparedStatement.addBatch() 的含义是把当前已经绑定好的参数组加入语句自己的 batch;它不会自动提交,也不会因为调用了 executeBatch() 就替你决定事务边界。事务仍由 Connection 控制。这样设计的好处是,SQL 模板只解析一次,参数按行替换,调用方可以把多个 batch 放进一个事务,也可以每个 batch 独立提交。

第 1 步:关闭自动提交并确定窗口
默认自动提交时,每条完成的更新都可能成为独立提交单元,批量调用就很难表达“这一窗口要一起成功”。先调用 connection.setAutoCommit(false),再根据业务选择窗口大小。订单导入通常可以按 500 或 1000 行划分窗口;真正的数值要结合单行大小、数据库日志和失败重试成本调整。
下面的示例把每个窗口作为一个事务。代码中的注释说明了提交边界、资源关闭和异常回滚的关系:
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.List;
record UserRow(long id, String name, String email) {}
static void insertUsers(Connection connection, List rows, int batchSize)
throws SQLException {
String sql = "INSERT INTO app_user (id, name, email) VALUES (?, ?, ?)";
// 关闭自动提交,让一个窗口内的写入由 commit/rollback 统一决定。
connection.setAutoCommit(false);
try (PreparedStatement statement = connection.prepareStatement(sql)) {
int inWindow = 0;
for (UserRow row : rows) {
// 每次循环只替换参数,不拼接 SQL,避免参数错位和 SQL 注入风险。
statement.setLong(1, row.id());
statement.setString(2, row.name());
statement.setString(3, row.email());
statement.addBatch();
inWindow++;
if (inWindow == batchSize) {
// 更新计数按加入顺序返回;执行成功后才提交当前窗口。
statement.executeBatch();
connection.commit();
statement.clearBatch();
inWindow = 0;
}
}
if (inWindow > 0) {
// 处理不足一个完整窗口的尾部数据,避免最后几行被遗漏。
statement.executeBatch();
connection.commit();
statement.clearBatch();
}
} catch (SQLException error) {
// 当前事务窗口失败时回滚;原异常继续抛给上层决定是否重试。
try {
connection.rollback();
} catch (SQLException rollbackError) {
error.addSuppressed(rollbackError);
}
throw error;
}
}
第 2 步:正确绑定每组参数
对 PreparedStatement 来说,addBatch() 不接受 SQL 字符串,它记录的是当前参数值。因此每次加入前都要覆盖所有参数位置。不要只修改“变化的字段”,否则某一行可能沿用上一行的旧值。
| 调用 | 作用 | 常见误区 |
|---|---|---|
setLong/setString | 绑定当前行参数 | 漏绑位置或复用错误索引 |
addBatch() | 保存一组参数 | 误以为已经写入数据库 |
executeBatch() | 提交当前语句的批处理请求 | 忽略返回的更新计数 |
clearBatch() | 清空已执行的语句批次 | 窗口循环时重复执行旧数据 |
第 3 步:把 executeBatch 放在提交之前
一个稳定的顺序是“加入参数 → 执行 batch → 提交事务 → 清空 batch”。executeBatch() 返回的数组与加入顺序对应,每个元素可能是实际更新行数,也可能是驱动无法提供精确数量时的 Statement.SUCCESS_NO_INFO。所以它适合用来记录窗口结果,但不能把所有驱动的返回值都当成精确行数。
如果业务要求整批原子成功,可以把多个 executeBatch() 都放到一次 commit() 前;如果更看重失败恢复速度,就按固定窗口提交。不要混用两种语义:调用方必须知道失败后是重做整个文件、重做当前窗口,还是只处理未确认的行。

第 4 步:处理 BatchUpdateException
批处理中某条命令失败时,驱动可能已经处理了前面的命令,也可能继续尝试后续命令;具体行为不能靠猜。捕获 BatchUpdateException 后,可以读取它携带的更新计数数组,并把 SQLState、错误码和当前窗口的业务范围记录下来。是否重试要先确认失败是临时连接问题、约束冲突,还是输入本身不可写。
try {
// 只执行当前窗口,不在异常分支里自动把同一批再次发送。
int[] counts = statement.executeBatch();
connection.commit();
System.out.println("window updates=" + counts.length);
} catch (java.sql.BatchUpdateException error) {
// 更新计数只用于诊断,不能据此假定驱动一定执行了剩余命令。
int[] counts = error.getUpdateCounts();
connection.rollback();
System.err.println("batch failed, processed markers=" + counts.length);
System.err.println("sqlState=" + error.getSQLState()
+ ", vendorCode=" + error.getErrorCode());
throw error;
} catch (SQLException error) {
// 非批处理异常同样回滚当前事务,再交给上层决定补偿策略。
connection.rollback();
throw error;
}
回滚只能撤销仍在当前事务里的变化。如果上一窗口已经提交,就不能期待后一个窗口的回滚把它一起撤销。因此需要在日志中记录窗口编号、首尾主键、输入来源和提交结果,重试时按窗口定位,而不是盲目重放全部数据。
第 5 步:选择合适的批量窗口
窗口越大,网络往返和提交次数通常越少,但单次失败的重试成本、事务日志压力和驱动内存占用也越高。可以先从固定窗口开始,并在日志中记录耗时、更新计数和异常类型,再根据真实负载调整。
| 场景 | 推荐边界 | 失败后的动作 |
|---|---|---|
| 少量配置变更 | 一个业务事务 | 整体回滚并提示调用方修正输入 |
| 大文件导入 | 固定行数窗口 | 记录窗口范围,只重做失败窗口 |
| 可幂等同步 | 按主键或业务键分组 | 校验幂等条件后再重试 |
| 约束冲突密集 | 较小窗口或先预校验 | 区分坏数据与临时故障,不自动无限重试 |
最后核对四个结果
完成实现后,重点确认:每行参数都在 addBatch() 前重新绑定;尾部不足一个窗口的数据也执行过;executeBatch() 成功后才 commit();任何失败路径都会回滚当前未提交窗口并保留原始异常。满足这四点,JDBC 批量写入的事务和参数语义就比较清楚。
常见问题
调用 addBatch 后是不是已经写入数据库?
不是。它只是把当前参数组加入语句的批列表,真正执行要等到 executeBatch()。
executeBatch 失败后能不能直接再次调用?
不要默认可以。先回滚当前事务,判断失败原因和驱动已经处理的范围,再决定按窗口重试还是修正坏数据后重新构造 batch。
为什么不一直把所有数据放进一个事务?
单事务容易带来更长的锁持有时间、更大的日志和更高的失败重做成本。大批量导入通常应使用可观测的固定窗口,但最终边界仍要服从业务原子性要求。
Java 模式匹配处理 null 分支的安全结构
- 上一篇
- Java 模式匹配处理 null 分支的安全结构
- 下一篇
- Python asyncio.to_thread 传递上下文变量的边界
-
- 文章 · java教程 | 1小时前 | Java · 并发编程 · 虚拟线程 · java future ExecutorService 虚拟线程
- Java 虚拟线程执行器的生命周期管理
- 140浏览 收藏
-
- 文章 · java教程 | 1小时前 |
- Java 模式匹配处理 null 分支的安全结构
- 162浏览 收藏
-
- 文章 · java教程 | 1小时前 | Java · HTTP · java httpclient HTTP重定向 post followRedirects
- Java HttpClient 重定向 POST 请求时为什么请求方法可能变化
- 207浏览 收藏
-
- 文章 · java教程 | 3小时前 | Java · Java教程 · java classpath module-info.java module-path 模块可见性
- Java 模块路径和类路径冲突的定位清单
- 210浏览 收藏
-
- 文章 · java教程 | 3小时前 | JSON · Java · 序列化 · 类型系统 · java 反序列化 jackson sealed interface 多态JSON JsonTypeInfo
- Java 多态 JSON 映射的类型边界
- 478浏览 收藏
-
- 文章 · java教程 | 3小时前 |
- Java Stream 分组后保持排序的收集方案
- 104浏览 收藏
-
- 文章 · java教程 | 3小时前 |
- Java HttpClient ofFileDownload 怎样保存响应文件名
- 365浏览 收藏
-
- 文章 · java教程 | 3小时前 | 超时 · Java ·
- Java HttpClient 分层超时的配置方法
- 344浏览 收藏
-
- 文章 · java教程 | 4小时前 |
- Java MethodHandle invokeExact 类型不匹配为什么会失败
- 494浏览 收藏
-
- 文章 · java教程 | 4小时前 |
- Java 虚拟线程遇到阻塞调用时的线程占用分析
- 401浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- PubMedQA
- 深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
- 505次使用
-
- H2O EvalGPT
- H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
- 563次使用
-
- LMArena
- LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
- 590次使用
-
- HELM
- 深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
- 541次使用
-
- MMBench
- MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
- 353次使用
-
- Go 数据库事务如何处理提交后副作用:Outbox、重试边界与幂等消费
- 2026-08-25 238浏览
-
- Go sql.Tx 如何让回滚在提交后不覆盖结果
- 2026-09-12 348浏览
-
- Go database/sql 忘记 Rows.Close 为什么会拖垮连接池:事务收尾与排查
- 2026-08-11 374浏览
-
- Go sql.Tx 提交成功后回滚错误的处理约定
- 2026-09-29 360浏览
-
- Mybatis源码-缓存机制
- 2023-01-22 397浏览
