Spring REST API & Data JPA
40/46
Bài 40 / 46~12 phútRelationships & TransactionsLộ trình · chặng 16/21Miễn phí… lượt xem

Transaction Propagation, Isolation và Tối Ưu Hóa readOnly

Làm chủ các chiến lược Propagation, 4 cấp độ Isolation ngăn chặn anomaly và kỹ thuật tối ưu hóa hiệu năng đọc dữ liệu với @Transactional(readOnly = true).

TL;DR: propagation thiết lập ranh giới giao dịch: REQUIRED (mặc định) gộp chung vào transaction hiện tại (cùng commit/rollback), trong khi REQUIRES_NEW mở một transaction độc lập (thích hợp cho Audit Log). isolation kiểm soát khả năng nhìn thấy dữ liệu giữa các luồng đồng thời: READ_COMMITTED ngăn Dirty Read, REPEATABLE_READ ngăn Non-repeatable Read, và SERIALIZABLE ngăn Phantom Read. Cấu hình readOnly = true giúp Hibernate tắt Dirty Checking và tối ưu bộ nhớ heap.

Các chiến lược Propagation điều khiển ranh giới Transaction ra sao?

Khi Service A gọi Service B, thuộc tính propagation quyết định B sẽ tham gia vào transaction của A hay mở một transaction độc lập:

PropagationKhi Caller chưa có TxKhi Caller đang có Tx T1Trường hợp áp dụng
REQUIRED (Mặc định)Tạo Tx T1 mớiTham gia chung vào T199% logic nghiệp vụ cần tính toàn vẹn All-or-Nothing.
REQUIRES_NEWTạo Tx T2 mớiTạm dừng T1, mở T2 riêngGhi Audit Log, gửi thông báo, tác vụ best-effort.
NESTEDTạo Tx T1 mớiTạo Savepoint trong T1Hỗ trợ rollback cục bộ về điểm lưu savepoint.

Đối chiếu hai propagation trên cùng một kịch bản: cột REQUIRED có auditService.log join T1 nên khi T1 rollback thì dòng audit biến mất theo, cột REQUIRES_NEW suspend T1 để mở T2 rồi commit T2 nên khi T1 rollback dòng audit vẫn còn

Cạm bẫy REQUIRED + try/catch gây UnexpectedRollbackException

Nhiều lập trình viên lầm tưởng việc bọc try-catch quanh lời gọi method con sẽ giúp method cha commit bình thường nếu method con thất bại:

Java
// SAI LAM NGHIEP VU:
@Transactional // T1
public Order placeOrder(OrderRequest req) {
    Order order = orderRepo.save(new Order(req));
    try {
        inventoryService.reserve(req.items()); // REQUIRED: Join T1 va nem loi
    } catch (Exception e) {
        log.warn("Giu kho that bai nhung van muon luu don hang");
    }
    return order; // NEM UnexpectedRollbackException TAI DAY!
}

Cơ chế bên dưới: Khi reserve() ném runtime exception, Spring Transaction Interceptor lập tức đánh dấu cờ rollback-only lên T1 ngay tại thời điểm xảy ra ngoại lệ. Cho dù method cha có catch lỗi, khi kết thúc method và Spring cố phát lệnh COMMIT, nó phát hiện cờ rollback-only và ném ngoại lệ UnexpectedRollbackException.

Khắc phục: Nếu muốn reserve() thất bại mà không ảnh hưởng tới placeOrder(), bắt buộc phải chuyển sang REQUIRES_NEW.

Trục thời gian của placeOrder: T1 mở, orderRepo.save chạy INSERT trong T1, inventoryService.reserve với propagation REQUIRED ném InventoryException, interceptor của reserve đánh dấu T1 rollback-only ngay tại đó, khối catch bắt được exception và chạy tiếp như không có gì, tới lúc return thì Spring cố commit T1 và ném UnexpectedRollbackException

Các cấp độ Isolation ngăn chặn những bất thường nào?

Isolation kiểm soát mức độ cô lập giữa các transaction chạy song song:

Isolation LevelDirty ReadNon-repeatable ReadPhantom ReadĐánh giá thực tế
READ_COMMITTED✅ Đã ngăn❌ Có thể bị❌ Có thể bịMặc định chuẩn của PostgreSQL. Cân bằng tối ưu giữa an toàn và hiệu năng.
REPEATABLE_READ✅ Đã ngăn✅ Đã ngăn❌ Có thể bị*Dùng cho tài chính, kế toán khi đọc số dư nhiều lần trong transaction.
SERIALIZABLE✅ Đã ngăn✅ Đã ngăn✅ Đã ngănKhóa nghiêm ngặt nhất, throughput giảm mạnh, dễ phát sinh Serialization Failure.

(PostgreSQL triển khai REPEATABLE_READ qua MVCC nên trên thực tế ngăn chặn được cả Phantom Read).

Java
// Ap dung cho nghiep vu chuyen tien doi hoi tinh nhat quan cao
@Transactional(isolation = Isolation.REPEATABLE_READ)
public void transferMoney(Long fromId, Long toId, BigDecimal amount) { ... }

Cơ chế bên dưới của thuộc tính readOnly = true là gì?

Khi khai báo @Transactional(readOnly = true), hệ thống kích hoạt 3 cơ chế tối ưu hóa:

Đối chiếu hai transaction: bên mặc định load entity rồi chụp snapshot mọi field, gọi setStatus, tới lúc flush thì so hiện trạng với snapshot và sinh câu UPDATE; bên readOnly true không chụp snapshot và đặt FlushMode MANUAL nên sau setStatus không có mốc nào để so và không câu UPDATE nào chạy

  1. Hibernate tắt Dirty Checking: Bỏ qua bước tạo bản sao snapshot trong bộ nhớ, tiết kiệm tới ~30% dung lượng RAM heap per entity.
  2. Thiết lập FlushMode.MANUAL: Ngăn chặn Hibernate tự động phát lệnh flush trước mỗi câu truy vấn.
  3. Gửi tín hiệu tới JDBC Driver: Đặt connection.setReadOnly(true), cho phép các proxy như PgBouncer tự động điều hướng kết nối tới database Read-Replica.

Cạm bẫy sửa dữ liệu trong readOnly Transaction

Nếu bạn vô tình gọi setter cập nhật entity trong một phương thức readOnly = true:

Java
@Transactional(readOnly = true)
public void updateStatus(Long id) {
    Order order = repo.findById(id).orElseThrow();
    order.setStatus(PAID); // HIBERNATE BO QUA, KHONG FLUSH UPDATE XUONG DB!
}

Mã nguồn vẫn chạy trơn tru, không có bất kỳ exception nào ném ra, nhưng dữ liệu hoàn toàn không được cập nhật xuống cơ sở dữ liệu (Silent No-Op).

Những cạm bẫy phổ biến cần tránh là gì?

  1. Dùng REQUIRES_NEW trong vòng lặp lớn: Mỗi lần gọi REQUIRES_NEW đều tốn tài nguyên xin kết nối mới từ connection pool, dễ gây cạn kiệt pool và làm chậm hệ thống.
  2. Lạm dụng Isolation SERIALIZABLE: Gây nghẽn cổ chai hiệu năng và phát sinh lỗi tranh chấp transaction liên tục.
  3. Quên ghi đè @Transactional ở tầng method: Khi Service class được đánh dấu @Transactional(readOnly = true), các method ghi (CUD) quên khai báo @Transactional riêng sẽ không thể lưu dữ liệu.

Tự kiểm tra kiến thức

Tự kiểm tra
0/5 câu đã trả lời
  1. Q1
    Tại sao khối try-catch ở method cha KHÔNG THỂ cứu vãn giao dịch khi method con dùng Propagation.REQUIRED ném một RuntimeException?
  2. Q2
    Chiến lược Propagation nào là phù hợp nhất cho tính năng ghi Audit Log để đảm bảo log luôn được lưu kể cả khi giao dịch nghiệp vụ chính bị lỗi rollback?
  3. Q3
    Cấp độ Isolation mặc định của hệ quản trị cơ sở dữ liệu PostgreSQL là gì?
  4. Q4
    Thiết lập @Transactional(readOnly = true) mang lại lợi ích kỹ thuật nào trong tầng Hibernate Persistence Context?
  5. Q5
    Điều gì xảy ra nếu bạn gọi setter thay đổi thuộc tính của một Entity bên trong phương thức được đánh dấu @Transactional(readOnly = true)?

Bài tiếp theo: Tổng Kết Module 06: Relationships & Transactions

Bài này có giúp bạn hiểu bản chất không?

Bài này đáng gửi cho bạn học cùng?

Copy link đã gắn nguồn — dán group, chat, hoặc LinkedIn.

Lộ trình Junior Java Backend Developer

Chặng 16/21 · Spring Data JPA — bài 19/20 của chặng

Ôn phỏng vấn

Bài này trả lời được các câu phỏng vấn sau — tự trả lời thử trước khi mở đáp án.

Thảo luận & góp ý

Hỏi đáp về bài này

Bài tiếp theo

Tổng Kết Module 06: Quan Hệ Thực Thể và Quản Trị Giao Dịch