Apache Derby Bài 30: Quản lý Giao dịch (Transaction) và tính chất ACID (Commit, Rollback)
1. Giao dịch (Transaction) là gì?
Trong thực tế, một nghiệp vụ kinh doanh hiếm khi chỉ gói gọn trong một câu lệnh SQL. Giả sử bạn đang xây dựng tính năng "Chuyển tiền ngân hàng" từ tài khoản A sang tài khoản B, quy trình này bắt buộc phải có 2 bước:
-
Trừ tiền ở tài khoản A (
UPDATE tai_khoan SET so_du = so_du - 1000 WHERE id = 'A'). -
Cộng tiền vào tài khoản B (
UPDATE tai_khoan SET so_du = so_du + 1000 WHERE id = 'B').
Điều gì xảy ra nếu bước 1 thành công, nhưng bước 2 hệ thống bị mất điện hoặc đứt mạng? Tiền của A đã bị trừ nhưng B không nhận được. Dữ liệu lúc này rơi vào trạng thái sai lệch nghiêm trọng.
Transaction (Giao dịch) sinh ra để giải quyết vấn đề này. Nó gom một nhóm các câu lệnh SQL lại thành một khối thống nhất (một đơn vị công việc logic). Khối này tuân theo nguyên tắc: Hoặc là tất cả cùng thành công, hoặc là không có gì xảy ra.
2. Tính chất cốt lõi của Giao dịch: ACID
Bất kỳ hệ quản trị cơ sở dữ liệu quan hệ nào (bao gồm cả Apache Derby) đều phải tuân thủ nghiêm ngặt 4 tính chất ACID để đảm bảo tính toàn vẹn của dữ liệu:
-
A - Atomicity (Tính nguyên tử): Một giao dịch là không thể chia cắt. Nếu một câu lệnh trong giao dịch thất bại, toàn bộ các câu lệnh trước đó sẽ bị hủy bỏ (hành động như chưa từng xảy ra).
-
C - Consistency (Tính nhất quán): Dữ liệu phải chuyển từ trạng thái hợp lệ này sang trạng thái hợp lệ khác. Mọi ràng buộc (như
CHECK, khóa ngoại) đều phải được thỏa mãn khi giao dịch kết thúc. -
I - Isolation (Tính cô lập): Khi nhiều người dùng cùng thực hiện giao dịch đồng thời, các giao dịch này không được phép can thiệp hay nhìn thấy dữ liệu đang làm dở dang của nhau cho đến khi chúng hoàn tất.
-
D - Durability (Tính bền vững): Khi một giao dịch đã được xác nhận (Commit) thành công, dữ liệu đó phải tồn tại vĩnh viễn trên ổ cứng, ngay cả khi máy chủ bị sập nguồn ngay giây tiếp theo. (Derby đảm bảo điều này qua cơ chế Write-Ahead Logging - WAL).
3. Các lệnh điều khiển Giao dịch trong SQL
Trong công cụ dòng lệnh ij của Derby, mặc định mỗi khi bạn gõ một câu lệnh INSERT, UPDATE hay DELETE và nhấn Enter, Derby sẽ tự động lưu vĩnh viễn ngay lập tức. Đây gọi là chế độ Auto-commit.
Để gom nhóm nhiều lệnh, bạn cần tắt Auto-commit và sử dụng hai lệnh sau:
-
COMMIT: Xác nhận thành công. Ghi vĩnh viễn mọi thay đổi của giao dịch hiện tại xuống ổ cứng và bắt đầu một giao dịch mới. -
ROLLBACK: Hủy bỏ giao dịch. Đảo ngược toàn bộ các thay đổi của giao dịch hiện tại, đưa dữ liệu về trạng thái ban đầu (thời điểm ngay trước giao dịch).
Ví dụ trên ij:
SQL
-- Tắt tự động commit
autocommit off;
-- Bắt đầu giao dịch chuyển tiền
UPDATE tai_khoan SET so_du = so_du - 1000 WHERE id = 'A';
UPDATE tai_khoan SET so_du = so_du + 1000 WHERE id = 'B';
-- Nếu mọi thứ ổn, xác nhận:
COMMIT;
-- Nếu thấy có lỗi, đảo ngược:
-- ROLLBACK;
4. Quản lý Giao dịch trong Java (JDBC)
Khi lập trình Java, việc kiểm soát Transaction được thực hiện trực tiếp thông qua đối tượng Connection. Quy trình chuẩn bao gồm 4 bước:
-
Tắt Auto-commit:
conn.setAutoCommit(false); -
Thực thi các lệnh SQL (
executeUpdate). -
Nếu tất cả thành công: Gọi
conn.commit(); -
Nếu có lỗi (
SQLException): Gọiconn.rollback();trong khốicatch.
Mã nguồn mẫu: Giao dịch chuyển tiền an toàn
Java
package com.hieu.derby;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class DerbyTransactionDemo {
private static final String JDBC_URL = "jdbc:derby://localhost:1527/ngan_hang_db";
public static void main(String[] args) {
// SQL trừ tiền
String sqlTruTien = "UPDATE tai_khoan SET so_du = so_du - ? WHERE so_tai_khoan = ?";
// SQL cộng tiền
String sqlCongTien = "UPDATE tai_khoan SET so_du = so_du + ? WHERE so_tai_khoan = ?";
try (Connection conn = DriverManager.getConnection(JDBC_URL)) {
// 1. TẮT AUTO-COMMIT ĐỂ BẮT ĐẦU TRANSACTION
conn.setAutoCommit(false);
try (PreparedStatement pstmtTru = conn.prepareStatement(sqlTruTien);
PreparedStatement pstmtCong = conn.prepareStatement(sqlCongTien)) {
System.out.println("Bắt đầu giao dịch chuyển tiền...");
// Bước 1: Trừ 500k từ tài khoản A
pstmtTru.setDouble(1, 500000.0);
pstmtTru.setString(2, "TK_A");
pstmtTru.executeUpdate();
// (Giả lập một lỗi hệ thống bất ngờ xảy ra ở đây)
// int x = 10 / 0;
// Bước 2: Cộng 500k vào tài khoản B
pstmtCong.setDouble(1, 500000.0);
pstmtCong.setString(2, "TK_B");
pstmtCong.executeUpdate();
// 2. NẾU KHÔNG CÓ LỖI -> LƯU VĨNH VIỄN
conn.commit();
System.out.println("=> Giao dịch thành công! Dữ liệu đã được Commit.");
} catch (Exception e) {
// 3. NẾU CÓ BẤT KỲ LỖI GÌ -> ĐẢO NGƯỢC TOÀN BỘ
System.err.println("=> Phát hiện lỗi: " + e.getMessage());
if (conn != null) {
try {
System.err.println("=> Đang tiến hành Rollback dữ liệu...");
conn.rollback();
System.err.println("=> Rollback thành công, dữ liệu an toàn.");
} catch (SQLException ex) {
System.err.println("Lỗi nghiêm trọng khi Rollback: " + ex.getMessage());
}
}
} finally {
// 4. TRẢ LẠI TRẠNG THÁI MẶC ĐỊNH CHO CONNECTION PHOOL
try {
conn.setAutoCommit(true);
} catch (SQLException ex) {
ex.printStackTrace();
}
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
5. Tổng kết
-
Transaction là cơ chế bảo vệ hệ thống khỏi sự chắp vá dữ liệu do lỗi phần mềm, lỗi logic hoặc sự cố phần cứng gây ra.
-
Luôn nhớ tắt Auto-commit khi thực hiện một chuỗi thao tác DML (
INSERT,UPDATE,DELETE) có liên quan logic chặt chẽ với nhau. -
Cặp lệnh
conn.commit()(trongtry) vàconn.rollback()(trongcatch) là nguyên tắc vàng để vận hành tính chất Atomicity (Nguyên tử) trong JDBC.
Khi nhiều giao dịch chạy song song cùng lúc, Derby làm sao để đảm bảo chúng không dẫm chân lên nhau (tính chất Isolation)? Bài tiếp theo sẽ giải mã cơ chế khóa tài nguyên của database: Apache Derby Bài 31: Các mức độ cô lập giao dịch (Transaction Isolation Levels) và hiện tượng Lock.
All rights reserved