JDBC 預設 自動提交模式:每條 SQL 獨立提交。關閉自動提交的三大理由:
Connection conn = dataSource.getConnection();
conn.setAutoCommit(false); // 1. 關閉自動提交
try {
// 業務 SQL 操作...
conn.commit(); // 2. 全部成功 → 提交
} catch (SQLException e) {
conn.rollback(); // 3. 任一失敗 → 回滾
throw e;
} finally {
conn.setAutoCommit(true); // 恢復預設,避免連接池污染
conn.close();
}
public class JdbcTransactionDemo {
static final String URL = "jdbc:mysql://localhost:3306/EMP";
static final String USER = "root";
static final String PASS = "password";
public static void main(String[] args) {
String sql1 = "INSERT INTO Employees VALUES (106, 20, 'Rita', 'Tez')";
String sql2 = "INSERTED INTO Employees VALUES (107, 22, 'Sita', 'Singh')"; // 故意寫錯
try (Connection conn = DriverManager.getConnection(URL, USER, PASS)) {
conn.setAutoCommit(false);
try (Statement stmt = conn.createStatement()) {
stmt.executeUpdate(sql1);
stmt.executeUpdate(sql2); // 拋出 SQLException
conn.commit();
System.out.println("提交成功");
} catch (SQLException e) {
conn.rollback();
System.out.println("回滾完成: " + e.getMessage());
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
輸出:回滾完成: You have an error in your SQL syntax...,兩條 INSERT 均不生效。
conn.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
// 可選:READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ, SERIALIZABLE
READ_COMMITTED(Oracle、PostgreSQL、SQL Server)或 REPEATABLE_READ(MySQL InnoDB)。Savepoint sp = conn.setSavepoint("BEFORE_BATCH");
try {
batchInsert(conn);
} catch (SQLException e) {
conn.rollback(sp); // 僅回滾到保存點
conn.releaseSavepoint(sp);
}
| 問題 | 後果 | 解決 |
|---|---|---|
| 忘記恢復 autoCommit | 連接歸還池時仍處於手動模式,後續借用者異常 | finally 中 conn.setAutoCommit(true) |
| 未關閉資源 | 連接洩漏、游標殘留 | try-with-resources 或 finally 關閉 Statement/ResultSet |
| 長事務持有連接 | 連接池耗盡、並發下降 | 縮小事務邊界、異步化非關鍵操作 |
rollback()。@Transactional 切面自動織入,異常自動回滾,配置集中管理傳播/隔離/超時/只讀屬性。💡 最佳實踐:DAO 層僅提供單表 CRUD,Service 層組合多 DAO 操作並加 @Transactional,實現事務邊界與業務邊界對齊。