@Service@TransactionalpublicclassOrderService{publicvoidplace(Order order){ orderRepository.save(order); eventPublisher.publishEvent(newOrderPlacedEvent(order.getId()));// ↓ 메일이 이미 발송됨validatePayment(order);// 여기서 예외 → 트랜잭션 롤백// DB는 롤백되지만 메일은 이미 나갔다! 데이터 불일치}}
@ComponentpublicclassOrderNotificationListener{@TransactionalEventListener(phase =TransactionPhase.AFTER_COMMIT)publicvoidhandle(OrderPlacedEvent event){ mailSender.send("주문 완료: "+ event.orderId());// 커밋 후에만 실행 → 롤백되면 안 나감}}
@TransactionalEventListener(phase =TransactionPhase.AFTER_COMMIT)@Transactional(propagation =Propagation.REQUIRES_NEW)publicvoidhandle(OrderPlacedEvent event){ notificationRepository.save(...);// 새 트랜잭션에서 안전하게 저장}
@Component@RequiredArgsConstructorpublicclassAdminNightNotificationListener{privatefinalRecoveryMailSender mailSender;@Async@TransactionalEventListener(phase =TransactionPhase.AFTER_COMMIT)publicvoidhandle(AdminNightNotificationEvent event){try{ mailSender.send(buildMail(event));}catch(Exception e){ log.warn("알림 전송 실패", e);}}}