
Spring Framework 를 사용한다면 ApplicationEventPublisher를 사용해 이벤트 발행을 시킬 수 있다.
만약 이벤트 대상 메서드에서 예외가 발생하면 어떻게 될까?


이벤트 메서드에서 예외가 던져지면 이벤트를 발행한 메서드까지 전파된다. @Async를 사용해 비동기 처리를 하지 않는 이상 같은 스레드에서 동기 방식으로 실행되기 때문이다.



그럼 TransactionalEventListener 에서도 마찬가지일까?
TransactionalEventListener
@TransactionalEventListener애너테이션은 이벤트 발행 메서드의 트랜잭션 상태에 따라 이벤트 호출 시점을 결정해 준다.

- BEFORE_COMMIT - 커밋 직전 후
- AFTER_COMMIT - 커밋 성공 후 호출
- AFTER_ROLLBACK - 롤백 후 호출
- AFTER_COMPLETION - 트랜잭션 종료(커밋/롤백) 후 호출
이름에서부터 명확하게 의도를 파악할 수 있다. 하지만 @EventListnere애너테이션에서의 예외 발생을 떠올려보면 한 가지 의문이 생긴다.
AFTER_~~이벤트에서 예외가 발생하면 예외가 전파될 텐데 롤백 처리는 어떻게 되는 거지?
BEFORE_COMMIT 이벤트에서의 예외 전파



역시 예상대로 foo 메서드까지 예외가 잘 전파되었다. 일반적인 롤백 로그는 아니지만 어쨌든 롤백 또한 잘 처리되었다.
그럼 AFTER 이벤트에선 어떨까?
AFTER 이벤트에서의 예외 전파

이벤트 호출 시점만 변경 후 동일하게 실행해 보았다.

예상과 달리 예외가 전파되지 않았다. 그 이유가 뭘까?
TransactionalEventListener의 동작 원리
우선 TransactionalEventListener 가 어떻게 동작되는지 알아볼 필요가 있다.



아까와 동일하게 출력을 남겨 보았다. 중간중간 이상한 로그도 껴있고 순서가 일치하지 않았다. 출력 순서만 본다면 foo() 메서드만 모두 실행 후 bar() 메서드가 호출된 느낌이다.


이벤트 메서드에 디버그 포인트를 찍고 보면 그 비밀을 알 수 있다. TransactionSynchronizationUtils의 invokeAfterCompletion를 통해 이벤트가 호출 된 것을 알 수 있다.
invokeAfterCompletion메서드 자체가 @Transactional로 인해 만들어지는 프록시의 트랜잭션 커밋 이후에 호출되기 때문에 순서가 절차적이지 않았다.

또한 메서드를 확인해 보면 try-catch를 통해 이벤트 메서드에서 던져진 예외는 단순히 로그 출력으로 변환해 예외 전파가 되지 않도록 하였다.

예외가 전파된 BEFORE_COMMIT은 try-catch 없이 단순 호출이기 때문에 예외가 그대로 전파되었다.
EventListener 등록
그럼 @EventListener나 @TransactionalEventListener는 어떻게 등록될까?

정답은 EventListenerMethodProcessor에 있다. 스프링 빈을 모두 생성하면 EventListenerMethodProcessor는 BeanFactory에서 @EventListener애너테이션이 붙은 메서드를 가져와 적절한 ApplicationListener에 등록해 준다.
이 과정에서 @EventListener와 @TransactionalEventListener의 호출 차이가 생긴다


지금까지 @TransactionalEventListener의 동작을 보면 유용하게 사용될 것 같다. 가령 REQUIRES_NEW전파 속성으로 트랜잭션을 분리하더라도 outer tx에서 try-catch 등으로 적절하게 예외 처리를 하지 않으면 서로 다른 트랜잭션 메서드 간 예외 간섭이 생길 수 있는데 @TransactionalEventListener를 사용하면 그런 불필요한 코드와 결합 없이도 트랜잭션을 잘 분리할 수 있을 것이다.
1줄 요약(TL;DR)
@TransactionalEventListener의 용빼는 재주 비밀은 TransactionSynchronizationUtils에 있다.