@EventListener vs @TransactionalEventListener: 어떤 차이일까?
스프링에서 이벤트 처리할 때 두 가지 어노테이션이 있는데요, 사실 처음에는 @EventListener만 썼다가 트랜잭션 문제로 @TransactionalEventListener를 알게 되었어요.
📌 @EventListener와 @TransactionalEventListener 비교표
| 기준 | @EventListener | @TransactionalEventListener |
|---|---|---|
| 실행 시점 | 이벤트 발생 즉시 실행 | 트랜잭션 상태에 따라 실행 |
| 트랜잭션 연관성 | 독립적으로 실행 | 현재 트랜잭션에 종속적 |
| 예외 처리 | 예외 발생 시 호출한 쪽에 전파 | phase에 따라 다름 (기본: AFTER_COMMIT) |
| 사용 사례 | 단순 이벤트 처리, 비즈니스 로직과 무관 | DB 상태 변경 후 후속 작업 필요 시 |
| 구현 난이도 | 단순함 | phase 설정으로 약간 복잡 |
회사에서 처음 개발할 때 @EventListener로 진행하였으나 모종의 이유(트랜잭션 롤백 문제...)로 @TransactionalEventListener로 변경되었는데요..
뭐가 이렇게 다른건지 알아봤습니다
가장 크게 두 가지 차이점이 있었습니다. 바로 1. 실행 시점 2. 트랜잭션 연관성
첫번째로 중요한 차이는 실행 시점
// 이벤트 발생 즉시 실행됨
@EventListener
public void handleEvent(UserRegisteredEvent event) {
emailService.sendWelcomeEmail(event.getUser());
}
// 트랜잭션이 커밋된 후에 실행됨
@TransactionalEventListener
public void handleEventAfterCommit(UserRegisteredEvent event) {
emailService.sendWelcomeEmail(event.getUser());
}
실행 흐름을 살펴보면, @EventListener는 이벤트가 발생하자마자 실행되는데 @TransactionalEventListener는 트랜잭션 상태에 따라 실행됩니다.
근데 두번째로 더 중요한건 트랜잭션 연관성! 이 부분을 이해하려면 TransactionPhase를 알아야해요.
그래서 TransactionPhase는 어떤게 있냐면요
public enum TransactionPhase {
BEFORE_COMMIT, // 트랜잭션 커밋 전
AFTER_COMMIT, // 트랜잭션 커밋 후 (기본값)
AFTER_ROLLBACK, // 트랜잭션 롤백 후
AFTER_COMPLETION // 트랜잭션 완료 후 (커밋이든 롤백이든)
}
기본값은 AFTER_COMMIT인데, 이 말은 "트랜잭션이 성공적으로 커밋되어야만 이벤트 리스너가 실행된다"는 의미예요.
실제 코드로 보자면:
@Service
public class UserService {
@Autowired
private ApplicationEventPublisher eventPublisher;
@Transactional
public void registerUser(User user) {
// 사용자 저장
userRepository.save(user);
// 이벤트 발행
eventPublisher.publishEvent(new UserRegisteredEvent(user));
// 만약 여기서 예외가 발생하면?
if (user.getEmail().contains("error")) {
throw new RuntimeException("의도적 에러 발생!");
}
}
}
위 코드에서:
- @EventListener: 이미 이메일 보냈는데 예외 발생해서 롤백되면 사용자는 생성 안 됐는데 이메일은 받음 🤦♂️
- @TransactionalEventListener: 트랜잭션이 커밋될 때만 이메일 발송되니 정합성 유지 👍
정리해보자면
📌 언제 무엇을 써야할까요?
| 상황 | 추천 어노테이션 | 이유 |
|---|---|---|
| 단순 알림 기능 | @EventListener | 트랜잭션과 무관하게 즉시 처리 필요 |
| 이메일 발송 | @TransactionalEventListener | 트랜잭션 성공 시에만 발송되어야 함 |
| 로깅 목적 | @EventListener | 성공/실패 모두 기록해야 할 때 |
| 외부 시스템 연동 | @TransactionalEventListener | 데이터 정합성이 중요할 때 |
| 실시간 처리 필요 | @EventListener | 지연 없이 즉시 처리 필요 |
| 대량 작업 트리거 | @TransactionalEventListener | 메인 트랜잭션 완료 후 비동기 처리 |
저라면 데이터 정합성이 중요한 경우에는 무조건 @TransactionalEventListener 쓸 것 같아요
저도 처음엔 그냥 @EventListener만 썼다가 트랜잭션 롤백 문제로 고생했지만 이제는 잘 구분해서 씁니다!
더 좋은 이벤트 처리 방법이 있다면 알려주시면 감사하겠습니다!
'Java, Spring' 카테고리의 다른 글
| [Java] Volatile 키워드와 멀티스레드 : 동시성 문제, 사용방법 (0) | 2024.01.17 |
|---|---|
| [Spring Boot] 스프링부트 JDBC logging 파헤치기 (로그설정, 로그레벨, JPA 쿼리) (0) | 2023.11.27 |
| [Java] 순차 스트림 (Sequential Stream)과 병렬 스트림 (Parallel Stream) & 스레드로 처리하기 (0) | 2023.11.14 |
| [JPA] Spring 3.0에서 queryDSL q파일 재생성하기(gradle) (0) | 2023.09.15 |
| [JPA] Entity->DTO, DTO->Entity 간의 매핑을 편리하게 해주는 MapStruct (0) | 2023.09.08 |