0

Apache Derby Bài 32: Trigger - Tự động hóa tác vụ cơ sở dữ liệu

1. Trigger (Trình kích hoạt) là gì?

Trong lập trình ứng dụng, chúng ta thường sử dụng mô hình "lắng nghe sự kiện" (Event Listener) – ví dụ: khi người dùng bấm nút, một đoạn mã JavaScript sẽ tự động chạy. Cơ sở dữ liệu cũng có một cơ chế tương tự gọi là Trigger.

Trigger là một tập hợp các câu lệnh SQL được gắn chặt vào một bảng vật lý. Nó sẽ tự động được "bóp cò" (thực thi) bởi Derby Engine mỗi khi có một sự kiện thao tác dữ liệu (INSERT, UPDATE, hoặc DELETE) xảy ra trên bảng đó.

Ứng dụng lớn nhất của Trigger là:

  • Ghi nhật ký kiểm toán (Audit Trail): Tự động lưu lại lịch sử thay đổi dữ liệu (Ai sửa? Sửa khi nào? Dữ liệu cũ là gì, mới là gì?).

  • Đồng bộ hóa dữ liệu: Tự động tính toán và cập nhật các bảng thống kê liên quan.

  • Kiểm tra tính toàn vẹn phức tạp: Chặn các thao tác vi phạm quy tắc nghiệp vụ mà ràng buộc CHECK thông thường không làm được.

2. Cấu trúc và Phân loại Trigger trong Derby

Một cú pháp tạo Trigger hoàn chỉnh bao gồm 4 yếu tố then chốt:

1. Thời điểm kích hoạt (Timing)

  • AFTER: Trigger chạy sau khi dữ liệu đã được cập nhật thành công vào bảng. Đây là loại phổ biến nhất, dùng để ghi log hoặc kích hoạt chuỗi hành động phụ.

  • NO CASCADE BEFORE: Trigger chạy trước khi dữ liệu kịp ghi xuống ổ cứng. Thường dùng để kiểm tra và ném ra ngoại lệ nhằm ngăn chặn thao tác (như một chốt chặn bảo vệ).

2. Sự kiện lắng nghe (Event)

Lắng nghe hành động INSERT, DELETE, hoặc UPDATE. Riêng với UPDATE, bạn có thể chỉ định theo dõi trên một cột cụ thể (UPDATE OF ten_cot).

3. Biến trạng thái (Transition Variables)

Đây là "linh hồn" của Trigger cấp dòng. Khi một dòng bị sửa, Derby cho phép bạn truy cập vào dữ liệu của dòng đó ở 2 trạng thái:

  • OLD: Giá trị cũ (trước khi bị sửa hoặc dòng sắp bị xóa).

  • NEW: Giá trị mới (vừa được chèn vào hoặc giá trị sắp cập nhật).

4. Mức độ bao phủ (Granularity)

  • FOR EACH ROW (Cấp độ dòng): Nếu lệnh UPDATE sửa 100 dòng, Trigger sẽ tự động chạy 100 lần (mỗi lần cho 1 dòng).

  • FOR EACH STATEMENT (Cấp độ lệnh): Dù lệnh UPDATE sửa 100 dòng hay 0 dòng, Trigger chỉ chạy duy nhất 1 lần cho cả câu lệnh đó.

3. Thực hành: Xây dựng hệ thống lưu vết thay đổi giá (Audit Trail)

Giả sử hệ thống thương mại điện tử yêu cầu: Mỗi khi nhân viên thay đổi giá bán của một sản phẩm, hệ thống phải tự động lưu lại giá cũ, giá mới và thời điểm sửa vào một bảng lịch sử để phục vụ kiểm toán.

Nếu làm bằng Java, bạn phải viết 2 lệnh SQL (UPDATE bảng sản phẩm, INSERT vào bảng lịch sử) và gói chúng trong một Transaction. Với Trigger, bạn chỉ cần cấu hình DB 1 lần duy nhất.

Bước 1: Chuẩn bị bảng dữ liệu

SQL

-- Bảng chứa dữ liệu hiện tại
CREATE TABLE san_pham (
    ma_sp VARCHAR(20) PRIMARY KEY,
    ten_sp VARCHAR(100),
    gia_ban DECIMAL(12, 2)
);

-- Bảng lưu vết lịch sử (Audit Table)
CREATE TABLE lich_su_gia (
    id INT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
    ma_sp VARCHAR(20),
    gia_cu DECIMAL(12, 2),
    gia_moi DECIMAL(12, 2),
    thoi_gian TIMESTAMP
);

Bước 2: Tạo Trigger lắng nghe sự kiện UPDATE

SQL

CREATE TRIGGER trg_luu_vet_gia
AFTER UPDATE OF gia_ban ON san_pham
REFERENCING OLD AS cu NEW AS moi
FOR EACH ROW
INSERT INTO lich_su_gia (ma_sp, gia_cu, gia_moi, thoi_gian)
VALUES (moi.ma_sp, cu.gia_ban, moi.gia_ban, CURRENT_TIMESTAMP);

(Giải nghĩa: Ngay sau khi cột gia_ban của bảng san_pham bị cập nhật, hãy lấy giá trị cũ (cu.gia_ban) và giá trị mới (moi.gia_ban) để chèn một dòng vào bảng lich_su_gia).

Bước 3: Kiểm thử sự kỳ diệu của Trigger Trên ứng dụng Java hoặc ij, bạn chỉ việc thao tác như bình thường:

SQL

-- Chèn sản phẩm mới (Trigger không chạy vì đây là INSERT)
INSERT INTO san_pham VALUES ('SP01', 'Laptop Dell', 15000000.00);

-- Tăng giá sản phẩm (Trigger sẽ tự động bị bóp cò ở hậu trường!)
UPDATE san_pham SET gia_ban = 16500000.00 WHERE ma_sp = 'SP01';

Lúc này, nếu bạn kiểm tra bảng lich_su_gia:

SQL

SELECT * FROM lich_su_gia;

Kết quả: Hệ thống đã âm thầm tạo ra một bản ghi với ma_sp = 'SP01', gia_cu = 15000000.00, gia_moi = 16500000.00 và thời gian hệ thống tại thời điểm đó.

4. Đặc điểm giao dịch của Trigger

Trigger trong Apache Derby chạy trong cùng một Transaction (giao dịch) với câu lệnh đã kích hoạt nó. Điều này vô cùng quan trọng:

  • Nếu câu lệnh UPDATE thành công, nhưng lệnh INSERT bên trong Trigger bị lỗi (ví dụ: do sai kiểu dữ liệu), Derby sẽ Rollback toàn bộ, bao gồm cả câu lệnh UPDATE gốc của bạn.

  • Nhờ tính chất này, dữ liệu giữa bảng gốc và bảng Audit luôn đảm bảo tính nhất quán (Consistency) tuyệt đối.

5. Cạm bẫy và Best Practices khi dùng Trigger

Mặc dù rất mạnh mẽ, Trigger thường được ví như "Con dao hai lưỡi" trong quản trị cơ sở dữ liệu:

  1. Hiệu ứng rùa bò (Performance Hit): Nếu bạn áp dụng Trigger FOR EACH ROW trên một bảng thường xuyên thực hiện Bulk Update (cập nhật hàng triệu dòng cùng lúc), hệ thống sẽ bị suy giảm hiệu năng nghiêm trọng do phải kích hoạt Trigger hàng triệu lần.

  2. Logic ẩn (Hidden Logic): Khi mã nguồn Java gọi lệnh UPDATE nhưng lại thấy dữ liệu ở một bảng khác bị thay đổi, lập trình viên không biết sự tồn tại của Trigger sẽ mất hàng giờ để debug. Do đó, chỉ dùng Trigger cho các tác vụ thuần túy về dữ liệu (như Audit log, Validation), tuyệt đối không dùng Trigger để thực thi logic nghiệp vụ (Business Logic).

  3. Quản lý Trigger: Để xóa Trigger khi không còn sử dụng, bạn dùng lệnh DROP TRIGGER ten_trigger.

Một điểm hạn chế của Trigger trong Derby là nó chỉ cho phép viết các câu lệnh SQL cơ bản bên trong. Khi muốn viết một Trigger phức tạp chứa logic rẽ nhánh IF-ELSE, vòng lặp, hoặc gọi API bên ngoài, Derby sẽ yêu cầu bạn nhúng mã nguồn Java trực tiếp vào cơ sở dữ liệu.

Đây chính là cầu nối để chúng ta bước sang cấp độ cao nhất của Apache Derby: kết hợp mã Java vào trong Engine SQL.


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí