Application
스프링 @TransactionalEventListener
팅리엔
2024. 3. 2. 03:50
@EventListener
@Transactional
public void publishEvent(String message) {
repository.save(message); // 롤백 됨
MyEvent myEvent = new MyEvent(message);
eventPublisher.publishEvent(myEvent); // 에러 발생
System.out.println("after event"); // 출력되지 않음
}
@EventListener
public void handleEvent(MyEvent myEvent) {
// 에러 발생
}
@EventListener + @Async
@Transactional
public void publishEvent(String message) {
repository.save(message); // 롤백 되지 않음
MyEvent myEvent = new MyEvent(message);
eventPublisher.publishEvent(myEvent); // 에러 발생
System.out.println("after event"); // 출력 됨
}
@Async
@EventListener
public void handleEvent(MyEvent myEvent) {
// 에러 발생
}
비동기. 별도의 스레드에서 처리된다. 별도의 트랜잭션을 가진다. 퍼블리셔의 트랜잭션은 롤백되지 않으며, 퍼블리셔는 이벤트 발행 후 다음 작업을 바로 수행한다.
@TransactionalEventListener
이 어노테이션을 사용하면 퍼블리셔의 트랜잭션 커밋 어느 시점에 이벤트가 처리될 것인지 지정할 수 있다.
- BEFORE_COMMIT
- AFTER_COMMIT (기본)
- AFTER_ROLLBACK
- AFTER_COMPLETION: 퍼블리셔의 트랜잭션 커밋 or 롤백 직후에 이벤트가 처리된다.
이걸 왜 쓰는 건지, 예시를 보겠다.
/* @EventListener 사용한 경우 */
@Transactional
public void publishEvent(String message) {
repository.save(message); // 롤백 됨
MyEvent myEvent = new MyEvent(message);
eventPublisher.publishEvent(myEvent); // 문제 없이 진행됨
throw new RuntimeException("무언가 문제 발생");
System.out.println("after event"); // 출력되지 않음
}
위와 같이 @EventListener를 사용한 경우, 이벤트 발행과 처리가 문제 없이 진행되고 난 후 예외가 발생해서 트랜잭션이 롤백되었다. 이런 상황을 원하지 않을 수가 있다. 예를 들어, 주문 완료 처리가 안 되고 롤백이 되었는데 주문 알람이 보내지는 걸 원치 않을 수 있다.
/* @TransactionalEventListener(phase=AFTER_COMMIT) 사용한 경우 */
@Transactional
public void publishEvent(String message) {
repository.save(message); // 롤백 됨
MyEvent myEvent = new MyEvent(message);
eventPublisher.publishEvent(myEvent); // 진행되지 않음
throw new RuntimeException("무언가 문제 발생");
System.out.println("after event"); // 출력되지 않음
}
그런 경우 @TransactionalEventListener(phase=AFTER_COMMIT)을 사용하면 트랜잭션이 커밋된 후 이벤트를 처리하게 할 수 있다.
[주의!!!]
phase를 AFTER_XXX로 설정하면, 트랜잭션이 완료된 상태에서 이벤트 처리가 시작된다. 하지만 트랜잭션 리소스(디비 커넥션)는 active한 상태일 수 있다(=트랜잭션은 완료되었지만, 트랜잭션이 사라진 건 아닌 상태). 만약 이벤트 처리 메서드에서 이미 존재하는 트랜잭션을 사용한다면 읽기 작업은 가능하지만 수정 작업은 불가능하다. 또한 이벤트 처리 메서드에서 에러가 발생해도 기존 트랜잭션은 롤백되지 않는다. (왜냐면 이미 커밋/롤백 되었으니까!)
새로운 트랜잭션을 시작하고 싶다면 트랜잭션 전파 수준을 조정하면 된다.
트랜잭션 전파 수준 참고 >
더보기
- REQUIRED:
기존 트랜잭션이 있으면 참여하고, 없으면 새로운 트랜잭션을 시작합니다.
기본값으로, 대부분의 경우에서 사용됩니다. - SUPPORTS:
기존 트랜잭션이 있으면 참여하고, 없으면 트랜잭션 없이 메서드를 실행합니다. - MANDATORY:
기존 트랜잭션이 있으면 참여하고, 없으면 예외가 발생합니다. - REQUIRES_NEW:
항상 새로운 트랜잭션을 시작하고, 기존 트랜잭션이 있으면 일시 중단시킵니다. - NOT_SUPPORTED:
트랜잭션 없이 메서드를 실행하고, 기존 트랜잭션이 있으면 일시 중단시킵니다. - NEVER:
트랜잭션 없이 메서드를 실행하고, 기존 트랜잭션이 있으면 예외가 발생합니다. - NESTED:
새로운 중첩 트랜잭션을 시작합니다. 이 중첩 트랜잭션은 독립적인 커밋 또는 롤백을 가질 수 있습니다.
@TransactionalEventListener + @Async
@Transactional
public void publishEvent(String message) {
repository.save(message); // 롤백 됨
MyEvent myEvent = new MyEvent(message);
eventPublisher.publishEvent(myEvent); // 진행되지 않음
throw new RuntimeException("무언가 문제 발생");
System.out.println("after event"); // 출력되지 않음
}
@Transactional
public void publishEvent(String message) {
repository.save(message); // 롤백 되지 않음
MyEvent myEvent = new MyEvent(message);
eventPublisher.publishEvent(myEvent); // 에러 발생
System.out.println("after event"); // 출력 됨
}
@Async
@TransactionalEventListener
public void handleEvent(MyEvent myEvent) {
// 에러 발생
}