JDBC 事務通過 Connection 控制,核心三方法:setAutoCommit、commit、rollback。本文詳解事務邊界控制、保存點部分回滾、經典轉賬案例及最佳實踐模板。
| 方法 | 作用 | 關鍵點 |
|---|---|---|
setAutoCommit(false) | 開啟事務 | 默認 true,每條 SQL 獨立事務 |
commit() | 提交事務 | 所有操作持久化 |
rollback() | 回滾事務 | 撤銷所有未提交操作 |
Connection con = null;
try {
con = dataSource.getConnection();
con.setAutoCommit(false); // 開啟事務
// 業務操作...
stmt1.executeUpdate(sql1);
stmt2.executeUpdate(sql2);
con.commit(); // 提交
} catch (Exception e) {
if (con != null) try { con.rollback(); } catch (SQLException ignored) {}
throw new RuntimeException(e);
} finally {
close(con, stmt, rs);
}
張三轉 1000 給李四,需兩條 SQL 原子執行:
public void transfer(boolean fail) {
Connection con = null;
PreparedStatement pstmt = null;
try {
con = JdbcUtils.getConnection();
con.setAutoCommit(false);
String sql = "UPDATE account SET balance = balance + ? WHERE id = ?";
pstmt = con.prepareStatement(sql);
// 張三 -1000
pstmt.setDouble(1, -1000);
pstmt.setInt(2, 1);
pstmt.executeUpdate();
if (fail) throw new RuntimeException("模擬網絡中斷");
// 李四 +1000
pstmt.setDouble(1, 1000);
pstmt.setInt(2, 2);
pstmt.executeUpdate();
con.commit();
} catch (Exception e) {
if (con != null) try { con.rollback(); } catch (SQLException ignored) {}
throw new RuntimeException(e);
} finally {
JdbcUtils.close(con, pstmt);
}
}
JDBC 3.0 引入,需數據庫支持。con.getMetaData().supportsSavepoints() 驗證。
// 設置保存點
Savepoint sp = con.setSavepoint();
// 操作 3、4...
// 若條件不滿足,回滾到保存點(事務未結束)
if (balance < 0) {
con.rollback(sp); // 僅撤銷 sp 後的操作
System.out.println("回滾到保存點,事務繼續");
}
// 最後提交整個事務
con.commit();
關鍵:
rollback(sp)不結束事務,僅撤銷保存點後的操作;必須最終調用commit()或rollback()結束事務。
@Test
public void conditionalTransfer() {
Connection con = null;
PreparedStatement pstmt = null;
try {
con = JdbcUtils.getConnection();
con.setAutoCommit(false);
String sql = "UPDATE account SET balance = balance + ? WHERE name = ?";
pstmt = con.prepareStatement(sql);
// 張三 -1W
pstmt.setDouble(1, -10000); pstmt.setString(2, "zs"); pstmt.executeUpdate();
// 李四 +1W
pstmt.setDouble(1, 10000); pstmt.setString(2, "ls"); pstmt.executeUpdate();
Savepoint sp = con.setSavepoint(); // 保存點 A
// 李四 -100W(承諾返還)
pstmt.setDouble(1, -1000000); pstmt.setString(2, "ls"); pstmt.executeUpdate();
// 張三 +100W
pstmt.setDouble(1, 1000000); pstmt.setString(2, "zs"); pstmt.executeUpdate();
// 檢查李四餘額
pstmt = con.prepareStatement("SELECT balance FROM account WHERE name = ?");
pstmt.setString(1, "ls");
ResultSet rs = pstmt.executeQuery();
rs.next();
double balance = rs.getDouble(1);
if (balance < 0) {
con.rollback(sp); // 回滾到保存點 A,李四不扣款,張三不收款
System.out.println("張三,你上當了!");
}
con.commit(); // 提交前兩步轉賬
} catch (Exception e) {
if (con != null) try { con.rollback(); } catch (SQLException ignored) {}
throw new RuntimeException(e);
} finally {
JdbcUtils.close(con, pstmt);
}
}