| 方法 | 作用 | 預設行為 |
setAutoCommit(boolean) | 開關自動提交 | true(每條 SQL 隱式提交) |
commit() | 提交事務 | 手動調用 |
rollback() | 回滾事務 | 手動調用 |
setSavepoint() | 設定保存點 | 支援嵌套回滾 |
releaseSavepoint() | 釋放保存點 | -- |
MySQL 存儲過程事務範例(純資料庫層)
CREATE PROCEDURE sp_create_user_with_role(
IN p_user_name VARCHAR(25), IN p_password VARCHAR(25),
IN p_gender VARCHAR(1), IN p_phone VARCHAR(11), IN p_role_id INT
)
BEGIN
DECLARE t_error INT DEFAULT 0;
DECLARE CONTINUE HANDLER FOR SQLEXCEPTION SET t_error = 1;
START TRANSACTION;
INSERT INTO users (user_name, password, gender, phone_no)
VALUES (p_user_name, p_password, p_gender, p_phone);
INSERT INTO user_role_relation (user_id, role_id)
VALUES (LAST_INSERT_ID(), p_role_id);
IF t_error = 0 THEN COMMIT; ELSE ROLLBACK; END IF;
END
驗證:第二條 SQL 違反非空約束 → 兩表均無新增記錄,體現原子性。
JDBC 手動事務控制完整代碼
public class JdbcTransactionDemo {
private static final String URL = "jdbc:mysql://192.168.0.105:3306/TEST?useSSL=false&serverTimezone=UTC";
private static final String USER = "root", PASS = "root";
static { try { Class.forName("com.mysql.cj.jdbc.Driver"); } catch (Exception e) { throw new RuntimeException(e); } }
public static void main(String[] args) {
executeTransaction();
}
private static void executeTransaction() {
String sqlUser = "INSERT INTO users (user_name, password, gender, phone_no) VALUES (?, ?, ?, ?)";
String sqlRole = "INSERT INTO user_role_relation (user_id, role_id) VALUES (?, ?)";
try (Connection conn = DriverManager.getConnection(URL, USER, PASS)) {
conn.setAutoCommit(false); // 關鍵:關閉自動提交
try (PreparedStatement psUser = conn.prepareStatement(sqlUser, Statement.RETURN_GENERATED_KEYS);
PreparedStatement psRole = conn.prepareStatement(sqlRole)) {
// 1. 插入用戶
psUser.setString(1, "zhangsan");
psUser.setString(2, "123456");
psUser.setString(3, "男");
psUser.setString(4, "11111111111");
psUser.executeUpdate();
// 獲取自增 ID
Long userId = null;
try (ResultSet rs = psUser.getGeneratedKeys()) {
if (rs.next()) userId = rs.getLong(1);
}
// 2. 插入角色關聯
psRole.setLong(1, userId);
psRole.setInt(2, 1);
psRole.executeUpdate();
conn.commit(); // 成功提交
System.out.println("事務提交成功,userId=" + userId);
} catch (SQLException e) {
conn.rollback(); // 失敗回滾
throw new RuntimeException("事務回滾: " + e.getMessage(), e);
}
} catch (SQLException e) {
throw new RuntimeException("連接/提交/回滾異常", e);
}
}
}
關鍵最佳實踐
- try-with-resources 自動關閉
Connection/Statement/ResultSet,杜絕洩漏。
- PreparedStatement 防 SQL 注入、利用預編譯緩存提升性能。
- 獲取自增鍵:
RETURN_GENERATED_KEYS + getGeneratedKeys(),跨資料庫通用。
- 異常處理:catch 塊中
rollback() 亦需 try-catch 包裹,防回滾拋異常掩蓋原始錯誤。
- 事務邊界清晰:業務邏輯方法層面控制事務,DAO 僅提供單條 SQL 執行。
常見誤區與避坑
| 誤區 | 後果 | 正確做法 |
忘記 setAutoCommit(false) | 每條 SQL 獨立提交,無法回滾 | 事務開始第一行關閉自動提交 |
| 多線程共用 Connection | 事務交織、數據錯亂 | Connection 線程不安全,每請求一連接 |
| catch 後未 rollback 直接拋異常 | 連接池歸還帶事務連接,污染池 | finally 或 catch 中確保 rollback + close |
| 在循環中逐條提交 | 性能差、部分成功難回滾 | 批次處理或整體事務 |
擴展:Spring @Transactional 聲明式事務
實際專案中極少直接用 JDBC API,而是:
@Service
@RequiredArgsConstructor
public class UserService {
private final UserMapper userMapper;
private final UserRoleMapper roleMapper;
@Transactional(rollbackFor = Exception.class)
public Long createUserWithRole(UserDTO dto) {
User user = new User(dto.getName(), dto.getPassword(), dto.getGender(), dto.getPhone());
userMapper.insert(user); // MyBatis 自動回填 ID
roleMapper.insert(new UserRole(user.getId(), dto.getRoleId()));
return user.getId();
}
}
Spring AOP 代理自動管理 Connection 生命週期、提交/回滾、異常轉換,開發者專注業務邏輯。
- 上一篇文章: Hibernate @Embeddable 與 <component> 映射詳解:組件模式實現值類型復用
- 下一篇文章: Java static 關鍵字全解析:靜態域、方法、代碼塊、導入與內部類五大用法
Copyright ©
程式碼之家 All Rights Reserved