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
invokeWithinTransaction(method, targetClass, invocation)ĐọcTransactionAttributevà chọn transaction manager.createTransactionIfNecessary()Mở transaction mới hoặc tham gia transaction hiện có theo propagation.invocation.proceedWithInvocation()Gọi interceptor tiếp theo rồi target method.- THROW:
completeTransactionAfterThrowing(txInfo, ex)Hoàn tất transaction theo rule rồi ném tiếp exception gốc. cleanupTransactionInfo()finallykhôi phục transaction info trước đó trên cả hai nhánh.- RETURN:
commitTransactionAfterReturning()Saufinally, gọitransactionManager.commit().
Mức 2 - zoom bảng rule
completeTransactionAfterThrowing()
Rule cụ thể thắng trước, default chỉ là fallback
txAttr.rollbackOn(ex)Annotation Spring tạoRuleBasedTransactionAttribute.- Tìm rule khớp gần nhất
rollbackForhoặcnoRollbackForcó inheritance depth nhỏ nhất thắng. - Có winnerRollback trừ khi winner là
NoRollbackRuleAttribute. - Không có winner: gọi
super.rollbackOn(ex)DefaultTransactionAttributetrảtruechoRuntimeExceptionhoặcError. true:transactionManager.rollback(status)Với participant, lời gọi này có thể chỉ đánh dấu transaction chung.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
- A mở physical transaction; B dùng REQUIREDB có logical scope riêng nhưng tham gia cùng physical transaction.
- RuntimeException thoát qua vỏ BVỏ B gọi
rollback(innerStatus)trước khi code A catch. processRollback(innerStatus, false)B là participant, không phải new transaction.doSetRollbackOnly(innerStatus)globalRollbackOnParticipationFailuremặc định làtrue.- A catch rồi return bình thườngCatch không xóa được global rollback-only đã gắn lên transaction chung.
commit(outerStatus)thấy global rollback-onlyGọiprocessRollback(outerStatus, true).- 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ùngException.classkhi đó 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.