Vì method con và method cha đang cùng một physical transaction (propagation mặc định REQUIRED).
- Khi method con ném
RuntimeException, interceptor của nó đánh dấu rollback-only lên transaction dùng chung rồi mới trả exception ra. - Method cha
catchđược exception nhưng không xóa được cái dấu đó; đến lúc commit, transaction manager thấy transaction đã bị đánh dấu nên hủy commit và némUnexpectedRollbackException.
java
@Transactional
public void placeOrder(Order order) {
orderRepository.save(order);
try {
auditService.record(order); // @Transactional(REQUIRED) -> same tx
} catch (Exception ignored) {
// tx is already marked rollback-only here
}
} // commit -> UnexpectedRollbackExceptionCách xử lý:
- Tách phần được phép hỏng sang transaction riêng: @Transactional(propagation = Propagation.REQUIRES_NEW) trên auditService.record.
- Hoặc dùng Propagation.NESTED nếu chỉ cần rollback tới savepoint và chạy tiếp.
- Hoặc đưa việc phụ ra ngoài transaction chính, ví dụ @TransactionalEventListener(phase = AFTER_COMMIT).
Bài học: bắt exception không đồng nghĩa với hủy tác động của nó lên transaction. Ranh giới transaction phải khớp với ranh giới xử lý lỗi.