@EventListener는 트랜잭션의 상태와 무관하게 이벤트를 수신하지만, @TransactionalEventListener는 이벤트 리스너가 트랜잭션 내에서 특정한 조건에 따라 동작하도록 합니다. 이를 통해, 예를 들어 트랜잭션이 성공적으로 커밋된 이후에만 이벤트를 처리하게 할 수 있습니다.
이 글에서 트랜잭션 상태에 따라 이벤트를 처리할 수 있는 @TransactionalEventListener 대해 알아보겠습니다. 🙂
@TransactionalEventListener
Spring이 제공하는 트랜잭션 이벤트 리스너입니다.
이 어노테이션을 사용하면 이벤트 리스너가 트랜잭션의 상태를 모니터링하며 이벤트를 처리할 수 있습니다.
@TransactionalEventListener 어노테이션은 다음과 같은 옵션들이 있습니다.

- ATFER_COMMIT: 트랜잭션 커밋 후에만 이벤트를 수신(기본값)
- AFTER_ROLLBACK: 트랜잭션 롤백 후에만 이벤트를 수신
- AFTER_COMPLETION: 트랜잭션 완료 후(커밋/롤백 상관없이) 이벤트를 수신
- BEFROE_COMMIT: 커밋 전에 이벤트를 수신
이 옵션들을 통해 트랜잭션의 다양한 상태에 따라 유연하게 이벤트를 처리할 수 있습니다.
사용 예시
트랜잭션 커밋 후 이벤트 처리(AFTER_COMMIT)
가장 일반적인 사용 예시로 트랜잭션이 성공적으로 완료된 후 이벤트를 처리하는 경우입니다.
예를 들어, 사용자가 회원가입 후 UserRegisterEvent라는 이벤트를 발행하고 해당 이벤트가 트랜잭션 커밋 이후에만 전송 메일을 발송하도록 설정할 수 있습니다!
1. 사용자 가입 이벤트 클래스 (UserRegisteredEvent)
- UserRegisteredEvent는 이벤트 정보를 담고 있는 단순한 데이터 클래스입니다. 이 클래스는 사용자의 이메일 주소를 담고 있으며, 이벤트 수신자에게 가입한 사용자의 이메일을 제공합니다.
public class UserRegisteredEvent {
private final String email;
public UserRegisteredEvent(String email) {
this.email = email;
}
public String getEmail() {
return email;
}
}
2. 이벤트 발행 클래스 (UserService)
- UserService는 사용자 가입을 담당하는 서비스 클래스입니다.
- registerUser 메서드에서 사용자 가입 로직을 처리하고, 이벤트를 발행하여 UserRegisteredEvent를 생성 및 전달합니다.
- @Transactional 어노테이션을 통해 가입 로직을 트랜잭션 내에서 실행하고, 가입이 정상적으로 완료된 경우에만 UserRegisteredEvent가 발행됩니다.
@Service
public class UserService {
private final ApplicationEventPublisher publisher;
@Transactional
public void registerUser(String email) {
// 회원가입 로직 (데이터베이스에 사용자 정보 저장 등)
publisher.publishEvent(new UserRegisteredEvent(email));
}
}
- publisher.publishEvent(new UserRegisteredEvent(email));를 통해 이벤트를 발행하며, 트랜잭션이 성공적으로 커밋되기 전까지는 UserEventListener가 이 이벤트를 처리하지 않습니다.
3. 이벤트 수신 클래스 (UserEventListener)
- UserEventListener는 이벤트 리스너로, 발행된 UserRegisteredEvent를 수신하여 환영 이메일을 발송하는 역할을 합니다.
- @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) 어노테이션을 사용하여 트랜잭션이 커밋된 후에만 이벤트가 처리되도록 합니다.
- 트랜잭션이 정상적으로 완료된 경우에만 sendWelcomeEmail 메서드가 호출되며, 콘솔에 환영 이메일 전송 메시지를 출력합니다.
@Component
public class UserEventListener {
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void sendWelcomeEmail(UserRegisteredEvent event) {
System.out.println("Sending welcome email to " + event.getEmail());
}
}
롤백 후 이벤트 처리(AFTER_ROLLBACK)
예를 들어, 결제 실패 시 사용자에게 알림을 보내야 하는 상황이 있다면 트랜잭션 롤백 후에 이벤트를 처리하도록 설정할 수 있습니다!
1. 이벤트 발행 (PaymentService 내 결제 처리 메서드):
- 결제 처리가 이루어지고 트랜잭션이 생성됩니다. 예를 들어, 주문의 결제 금액을 차감하거나, 결제 정보를 저장하는 작업이 포함될 수 있습니다.
- 트랜잭션 내에서 결제에 실패한 경우 PaymentFailedEvent 이벤트를 발행하고, 이후 트랜잭션이 롤백됩니다.
@Service
public class PaymentService {
private final ApplicationEventPublisher publisher;
@Transactional
public void processPayment(Order order) {
try {
// 결제 처리 로직 수행
// 예: 결제 금액 차감, 결제 정보 저장
if (/*결제 실패 조건*/) {
throw new PaymentException("결제 실패");
}
} catch (Exception e) {
// 결제 실패 시 이벤트 발행
publisher.publishEvent(new PaymentFailedEvent(order.getOrderId()));
throw e; // 트랜잭션 롤백을 위한 예외 재발생
}
}
}
2. 이벤트 리스너 (PaymentEventListener):
- @TransactionalEventListener의 TransactionPhase.AFTER_ROLLBACK 설정에 따라, 트랜잭션이 롤백된 후에만 PaymentFailedEvent를 수신하게 됩니다.
- 결제 실패 시에는 System.out.println("Payment failed for " + event.getOrderId());를 통해 결제 실패 메시지를 출력하거나, 실패한 결제에 대해 로그를 남기거나 알림을 발송할 수 있습니다.
@Component
public class PaymentEventListener {
@TransactionalEventListener(phase = TransactionPhase.AFTER_ROLLBACK)
public void handleFailedPayment(PaymentFailedEvent event) {
System.out.println("Payment failed for " + event.getOrderId());
}
}
3. 트랜잭션 상태에 따른 이벤트 처리:
- TransactionPhase.AFTER_ROLLBACK 설정 덕분에 결제가 성공적으로 완료된 경우에는 PaymentFailedEvent를 무시하고, 결제 실패 시에만 이벤트가 처리됩니다. 이 방식으로, 실패한 트랜잭션에 대해서만 특정 작업을 수행할 수 있게 되며, 코드의 의도와 명확히 맞아떨어지게 됩니다.
장점
- 트랜잭션 안정성: 트랜잭션이 성공적으로 완료된 후에만 이벤트가 처리되므로, 실패한 트랜잭션에 대한 불필요한 작업을 방지할 수 있습니다. 예를 들어, 데이터 저장이 실패했을 때는 후속 작업인 알림 전송이나 로그 기록을 건너뛸 수 있습니다.
- 코드 가독성 향상: 비즈니스 로직과 이벤트 처리 로직을 분리함으로써, 코드가 더 읽기 쉬워지고 유지보수가 용이합니다.
- 구체적인 트랜잭션 상태 제어: TransactionPhase 옵션을 사용해 AFTER_COMMIT, AFTER_ROLLBACK 등의 상태에 따라 이벤트를 다르게 처리할 수 있어 유연성이 높습니다.
- 비동기 처리가 가능: 필요에 따라 @Async와 함께 사용해 트랜잭션 이벤트를 비동기로 처리할 수 있습니다. 이로 인해 중요한 작업은 트랜잭션 내에서 마무리하고, 부가적인 작업(이메일 전송 등)은 비동기로 진행할 수 있습니다.
단점
- 트랜잭션 의존성: 트랜잭션이 없는 경우 @TransactionalEventListener는 이벤트를 처리하지 않습니다. 즉, 트랜잭션 범위 외에서 발생하는 이벤트가 있다면 @EventListener와 같이 별도로 관리해야 합니다.
- 복잡성 증가: TransactionPhase 설정을 잘못하면 예기치 않은 결과가 발생할 수 있습니다. 예를 들어, AFTER_COMMIT으로 설정했지만 트랜잭션이 롤백되는 경우 해당 이벤트가 무시되는 등의 상황이 발생할 수 있습니다.
- 비동기 이벤트 예외 처리 주의 필요: 비동기로 이벤트를 처리할 때 예외가 발생하면 해당 예외가 상위 트랜잭션으로 전파되지 않으므로 예외 처리를 위한 별도의 로직이 필요할 수 있습니다.
- 성능 저하 가능성: 많은 트랜잭션 이벤트를 비동기로 발행하는 경우, 리스너에서 발생하는 이벤트가 많아지면 성능에 영향을 줄 수 있습니다. 이 경우 스레드 풀 설정 등을 통해 적절한 제어가 필요합니다.
🚨 주의 사항
- 비동기 처리: @TransactionalEventListener는 기본적으로 동기로 작동하므로, 비동기 처리가 필요하다면 @Async를 함께 사용하여 비동기 이벤트 처리를 설정해야 합니다.
- 트랜잭션 전파: 이벤트가 트랜잭션 내에서 처리되므로, 트랜잭션 전파 방식에 주의해야 합니다. 특히 이벤트 리스너에서 데이터베이스 접근 시 새로운 트랜잭션을 생성하는 경우가 아니면, 같은 트랜잭션 내에서 동작합니다.
'Backend > Spring' 카테고리의 다른 글
| [Spring] @EventListener VS @TransactionEventListener (0) | 2024.10.26 |
|---|---|
| [Spring] 스프링에서 이벤트 발행과 구독 @EventListener (0) | 2024.10.25 |
| [Spring] HikariCP 동작 방식 (1) | 2024.09.29 |
| [Spring] HikariCP란? (1) | 2024.09.27 |
| [Spring] Bean Scope(빈 스코프) 란? (0) | 2024.09.21 |