Spring 이벤트를 트랜잭션 커밋 이후에만 처리하게 하는 리스너.
왜 필요한가 — 그냥 @EventListener의 문제
@Transactional
void order() {
repo.save(o);
publisher.publishEvent(new OrderPlaced(o.getId())); // 동기·같은 트랜잭션에서 실행
}
@EventListener
void sendMail(OrderPlaced e) { mailClient.send(...); } // 여기서 실패하면?
- ① 리스너가 실패하면 주문까지 롤백된다 (메일 때문에 주문이 취소된다)
- ② 아직 커밋 전인데 메일이 나간다 → 롤백되면 '없는 주문' 의 메일이 발송된 상태
해결 — 커밋 이후로 미룬다
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
void sendMail(OrderPlaced e) { mailClient.send(...); }
- BEFORE_COMMIT — 커밋 직전
- AFTER_COMMIT — 커밋 성공 후 (기본값·가장 많이 쓴다)
- AFTER_ROLLBACK — 롤백된 경우
- AFTER_COMPLETION — 성공·실패 무관
AFTER_COMMIT의 함정 — 여기서 DB를 쓰면 저장되지 않는다
@TransactionalEventListener(phase = AFTER_COMMIT)
void grantPoint(OrderPlaced e) {
pointRepo.save(new Point(...)); // 트랜잭션이 이미 끝났다 → 반영되지 않는다
}
// 새 트랜잭션을 명시해야 한다
@TransactionalEventListener(phase = AFTER_COMMIT)
@Transactional(propagation = Propagation.REQUIRES_NEW)
void grantPoint(OrderPlaced e) { pointRepo.save(...); }
비동기로 돌리려면
@Async // 별도 스레드 (@EnableAsync 필요)
@TransactionalEventListener(phase = AFTER_COMMIT)
void sendMail(OrderPlaced e) { ... }
단 비동기로 만드는 순간 유실 가능성이 생긴다 — 서버가 그 사이 죽으면 이벤트가 사라진다. 반드시 도달해야 하면 Outbox 패턴으로 간다.
어디까지 이걸로 하나
한 프로세스 안의 결합 분리 → Spring 이벤트로 충분하다
- 다른 서비스로 전달 · 유실 불가 — → Kafka/RabbitMQ + Outbox
"일단 Spring 이벤트로 경계를 만들어 두고, 필요해지면 메시지 브로커로 바꾼다" 가 실용적인 진화 경로다. 발행·구독 코드 모양이 비슷해 전환 비용이 낮다.
면접 함정
- ❌ "@EventListener는 비동기" → 기본은 동기이고 같은 트랜잭션에서 돈다.
@Async를 붙여야 비동기다. - ❌ "AFTER_COMMIT이면 안전하다" → 그 안의 DB 쓰기는 새 트랜잭션이 필요하고, 비동기면 유실될 수 있다.
이벤트를 언제 발행하나 — 도메인 안에서
@Entity
class Order extends AbstractAggregateRoot<Order> { // Spring Data 제공
void complete() {
this.status = COMPLETED;
registerEvent(new OrderCompleted(this.id)); // 아직 발행되지 않는다
}
}
// repository.save(order) 시점에 모아 둔 이벤트가 발행된다
서비스가 아니라 도메인이 이벤트를 등록하게 하면, "주문이 완료되면 이벤트가 난다"는 규칙이 도메인 안에 남는다.
트랜잭션이 없으면 아예 실행되지 않는다
@TransactionalEventListener // 트랜잭션 밖에서 발행하면 그냥 무시된다
void on(OrderPlaced e) { ... }
@TransactionalEventListener(fallbackExecution = true) // 트랜잭션이 없어도 실행
void on(OrderPlaced e) { ... }
테스트에서 이벤트가 안 불려 당황하는 흔한 원인이다. 트랜잭션 없이 발행했기 때문이다.