java jdbc 实现批量操作补偿的核心做法是关闭自动提交,在 try 块中执行 addbatch() 和 executebatch(),并在 catch 块中调用 rollback() 回滚事务。如果业务要求部分成功(即失败的数据跳过,成功的保留),则需要在循环中逐条执行并捕获异常,或者使用数据库特有的语法(如 mysql 的 insert ignore)来规避全量回滚。

标准事务如何保证数据一致性
标准事务通过原子性确保所有操作要么全部成功,要么全部失败。在 JDBC 中,这意味着一旦某条 SQL 语句抛出异常,整个批次都会失效,数据库状态回到执行前的样子。

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
public class StandardBatch {
public static void main(String[] args) {
String url = "jdbc:mysql://localhost:3306/test";
String user = "root";
String password = "password";
try (Connection conn = DriverManager.getConnection(url, user, password)) {
// 1. 关闭自动提交
conn.setAutoCommit(false);
String sql = "INSERT INTO users (name, email) VALUES (?, ?)";
try (var pstmt = conn.prepareStatement(sql)) {
// 2. 添加批量数据
pstmt.setString(1, "Alice");
pstmt.addBatch();
pstmt.setString(1, "Bob");
pstmt.addBatch();
pstmt.setString(1, "Charlie"); // 假设这行会导致主键冲突或格式错误
pstmt.addBatch();
// 3. 执行批量
int[] results = pstmt.executeBatch();
// 4. 手动提交
conn.commit();
System.out.println("Batch executed successfully. Results: " + java.util.Arrays.toString(results));
} catch (SQLException e) {
// 5. 发生异常时回滚
conn.rollback();
System.err.println("Transaction rolled back due to error: " + e.getMessage());
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
预期结果:如果 Charlie 插入失败,控制台打印 Transaction rolled back...,数据库中不会新增 Alice、Bob 或 Charlie。如果全部成功,打印 Batch executed successfully...,三条记录均入库。
如何实现部分成功的补偿逻辑
当需要“部分成功”时,不能依赖 executeBatch() 的原子性,而应改为逐条处理。在循环中调用 executeUpdate(),捕获单条异常后继续下一条,最后统一提交。这种模式称为“重试与跳过”策略。

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
public class PartialSuccessBatch {
public static void main(String[] args) {
String url = "jdbc:mysql://localhost:3306/test";
String user = "root";
String password = "password";
List<string> data = new ArrayList();
data.add(new String[]{"Alice", "alice@test.com"});
data.add(new String[]{"Bob", "bob@test.com"});
data.add(new String[]{"Charlie", "invalid-email"}); // 模拟错误数据
data.add(new String[]{"David", "david@test.com"});
try (Connection conn = DriverManager.getConnection(url, user, password)) {
conn.setAutoCommit(false);
String sql = "INSERT INTO users (name, email) VALUES (?, ?)";
int successCount = 0;
int failCount = 0;
try (var pstmt = conn.prepareStatement(sql)) {
for (String[] record : data) {
try {
pstmt.setString(1, record[0]);
pstmt.setString(2, record[1]);
pstmt.executeUpdate(); // 逐条执行
successCount++;
} catch (SQLException e) {
failCount++;
System.err.println("Failed to insert " + record[0] + ": " + e.getMessage());
// 不抛出异常,继续下一条
}
}
// 即使有失败,也提交已成功的部分
conn.commit();
}
System.out.println("Success: " + successCount + ", Failed: " + failCount);
} catch (SQLException e) {
e.printStackTrace();
}
}
}</string>
预期结果:控制台打印 Failed to insert Charlie: ...,随后打印 Success: 3, Failed: 1。数据库中存入 Alice、Bob 和 David,Charlie 被跳过。
基于主键冲突时的具体补偿行为
当批量操作中遇到主键冲突(Duplicate Key)时,JDBC 驱动会抛出 SQLIntegrityConstraintViolationException。在标准模式下,这会触发全量回滚;在逐条模式下,该条记录被跳过,后续记录继续执行。若需更高效的补偿,可使用数据库层面的 ON DUPLICATE KEY UPDATE(MySQL)或 MERGE INTO(Oracle)语法,将冲突视为更新而非错误。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











