본문 바로가기

⛳️ 공동구매 서비스 총대마켓

트랜잭션이 롤백될 때 이미 발행된 이벤트를 어떻게 처리할까: @TransactionalEventListener

1. 문제: 발행된 이벤트는 무조건 처리된다.

동시성 이슈를 해결하기 위해 낙관적 잠금을 구현하고 있었다.

 

충돌을 방지하기 위해 낙관적 잠금을 걸게 되면 애플리케이션 단에서 버저닝 필드를 검증한다. 그 후 버저닝 필드가 일치하지 않으면 트랜잭션 충돌로 판단하여 아래와 같이 낙관적 잠금 예외를 발생시키게 된다. 이 예외는 트랜잭션이 커밋되는 시점에 발생하는데, 그렇다면 예외가 발생해 트랜잭션이 롤백된다면 트랜잭션 내부에서 이전에 발행된 이벤트는 어떻게 처리될까?

 

총대마켓의 기존 코드에서는 트랜잭션이 롤백되어도 이미 발행된 이벤트는 실행되었다. 즉, 사용자가 참여하려고 할 때 공동구매가 마감되어 참여하지 못했는데(동시성 이슈로 인한 트랜잭션 롤백), "에버님이 공동구매에 참여했어요!"라는 알림이 전송(이벤트 실행)되는 꼴이다.

 

이 문제를 해결하기 위해, 예외가 발생하여 호출 메서드의 트랜잭션이 롤백되는 경우 발행된 이벤트가 실행되지 않도록 하자.

 

2. 기존 코드 상황: @EventListener로 발행 즉시 처리

기존 코드에 따르면 이벤트는 @EventListener를 통해 발행 즉시 처리된다. 즉 트랜잭션의 상태와 무관하게 아래에서 정의된 로직이 실행된다.

@EventListener
@Async
public void handleParticipateEvent(ParticipateEvent event) {
    // ...
}

 

3. 해결 방법: @TransactionalEventListener로 트랜잭션 상태에 따라 처리

@TransactionalEventListener를 적용하고 phase를 default인 AFTER_COMMIT으로 설정하여, 트랜잭션이 커밋되어야만 이벤트가 처리되도록 한다. 덕분에 이벤트가 발행된 후 트랜잭션이 롤백되면, 트랜잭션이 커밋되지 않은 상황이므로 이벤트가 발행되어도 처리되지 않는다.

@TransactionalEventListener(phase = AFTER_COMMIT)
@Async
public void handleParticipateEvent(ParticipateEvent event) {
    // ...
}

 

이것이 해결 방법의 전부이다. 하지만 @TransactionalEventListener에 대해 더 알아보고 싶어 학습테스트를 구현해 학습해 보았다. 관련 글은 아래를 참고하면 된다.

 

 

2024.12.23 - [⛳️ 공동구매 서비스 총대마켓] - 👀 @TransactionalEventListener: 학습테스트로 동작 방식 확인해보기

 

👀 @TransactionalEventListener: 학습테스트로 동작 방식 확인해보기

@TransactionalEventListener로 문제를 해결한 경험이 있다. 하지만 문제 해결에 그치고 싶지 않았고 이 어노테이션의 동작 방식을 더 깊이 알아보고 싶었다. 그 학습 여정을 공유하는 글이다. @Transaction

helenason.tistory.com