SPRING SERIES - LỖI IM LẶNG

Phán Quyết Của Transaction Proxy

PROXY - XANH = TẦNG PHÓNG TO Đọc từ trái sang phải. Màu đậm dần nghĩa là gần cơ chế hơn. Dấu * đánh dấu điểm bài viết khai thác.

Kết quả A - exception thoát ra nhưng commit

Checked exception không khớp rollback rule mặc định.

Cùng giao thức, khác cửa thấy lỗiRollback phụ thuộc rule tại từng transactional boundary và trạng thái transaction đã tích lũy.

Kết quả B - exception bị catch nhưng rollback

Vỏ trong đã đánh dấu global rollback-only trước khi vỏ ngoài commit.

Mức 1 - around advice đồng bộ

TransactionInterceptor.invoke()

Đường chuẩn với PlatformTransactionManager

  1. invokeWithinTransaction(method, targetClass, invocation)Đọc TransactionAttribute và chọn transaction manager.
  2. createTransactionIfNecessary()Mở transaction mới hoặc tham gia transaction hiện có theo propagation.
  3. invocation.proceedWithInvocation()Gọi interceptor tiếp theo rồi target method.
  4. THROW: completeTransactionAfterThrowing(txInfo, ex)Hoàn tất transaction theo rule rồi ném tiếp exception gốc.
  5. cleanupTransactionInfo()finally khôi phục transaction info trước đó trên cả hai nhánh.
  6. RETURN: commitTransactionAfterReturning()Sau finally, gọi transactionManager.commit().
Mức 2 - zoom bảng rule

completeTransactionAfterThrowing()

Rule cụ thể thắng trước, default chỉ là fallback

  1. txAttr.rollbackOn(ex)Annotation Spring tạo RuleBasedTransactionAttribute.
  2. Tìm rule khớp gần nhấtrollbackFor hoặc noRollbackFor có inheritance depth nhỏ nhất thắng.
  3. Có winnerRollback trừ khi winner là NoRollbackRuleAttribute.
  4. Không có winner: gọi super.rollbackOn(ex)DefaultTransactionAttribute trả true cho RuntimeException hoặc Error.
  5. true: transactionManager.rollback(status)Với participant, lời gọi này có thể chỉ đánh dấu transaction chung.
  6. false: transactionManager.commit(status)Checked exception mặc định đi nhánh này rồi vẫn được ném tiếp ra caller.

Spring 6.2+: default trên có thể đổi toàn cục bằng @EnableTransactionManagement(rollbackOn = RollbackOn.ALL_EXCEPTIONS). Rule riêng trên method vẫn được ưu tiên.

Mức 3 - zoom participant rollback

AbstractPlatformTransactionManager

Đường REQUIRED mặc định tới global rollback-only

  1. A mở physical transaction; B dùng REQUIREDB có logical scope riêng nhưng tham gia cùng physical transaction.
  2. RuntimeException thoát qua vỏ BVỏ B gọi rollback(innerStatus) trước khi code A catch.
  3. processRollback(innerStatus, false)B là participant, không phải new transaction.
  4. doSetRollbackOnly(innerStatus)globalRollbackOnParticipationFailure mặc định là true.
  5. A catch rồi return bình thườngCatch không xóa được global rollback-only đã gắn lên transaction chung.
  6. commit(outerStatus) thấy global rollback-onlyGọi processRollback(outerStatus, true).
  7. Ném UnexpectedRollbackExceptionMặc định xuất hiện ở outermost boundary vì failEarlyOnGlobalRollbackOnly=false.

Phép kiểm: breakpoint tại completeTransactionAfterThrowing(). Với kịch bản lồng, xem thêm isNewTransaction(), isGlobalRollbackOnly() và nơi processRollback(status, true) ném lỗi.

Giải pháp an toàn

  • Chọn rule theo contract nghiệp vụƯu tiên rollbackFor = InsufficientStockException.class; chỉ dùng Exception.class khi đó là policy thật.
  • Chọn policy toàn hệ thống có chủ đíchSpring 6.2+ hỗ trợ rollbackOn = RollbackOn.ALL_EXCEPTIONS; công việc độc lập cần boundary riêng, xem 2.2.

Khi KHÔNG được kết luận

  • Checked exception luôn commitRule riêng, global default hoặc rollback-only có thể buộc rollback.
  • Catch exception luôn cứu được transactionKhông đúng nếu exception đã vượt qua transactional boundary và participant đã đánh dấu rollback-only.