Spring 7기 프로젝트/모임 플렛폼 프로젝트
Spring의 @TransactionalEventListener와 새로운 트랜잭션 전파
JuNo_12
2025. 8. 5. 17:00
문제 상황
회원탈퇴 기능을 구현하면서 아웃박스 패턴을 사용하던 중, 다음과 같은 오류가 발생했습니다.
TransactionRequiredException: Executing an update/delete query
오류 발생 원인
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)를 사용하여 이벤트를 처리할 때, 기존 트랜잭션이 이미 커밋되어 종료된 상태에서 새로운 데이터베이스 업데이트 작업을 시도했기 때문입니다.
이벤트 처리 플로우
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void handleUserWithdrawn(UserEvents.Withdrawn event) {
// 1. RabbitMQ로 메시지 발행
userEventPublisher.publishUserWithdrawn(event.userId(), event.email(), event.nickname());
// 2. 아웃박스 상태 업데이트 시도 - 이 시점에서 트랜잭션이 없음!
userOutboxService.markEventAsPublished(event.userId(), "USER_WITHDRAWN");
}
해결 방법: REQUIRES_NEW 전파 속성 사용
markEventAsPublished 메서드에 @Transactional(propagation = Propagation.REQUIRES_NEW)를 적용하여 새로운 독립적인 트랜잭션을 시작하도록 했습니다.

트랜잭션 전파 속성 선택 기준
각 메서드별 적절한 트랜잭션 설정
- markEventAsPublished: REQUIRES_NEW
- 이유: @TransactionalEventListener(AFTER_COMMIT) 후 호출되므로 기존 트랜잭션이 이미 종료됨
- 새로운 독립적인 트랜잭션이 필요
- saveUserWithdrawnEvent: 일반 @Transactional
- 이유: 회원탈퇴 비즈니스 로직과 같은 트랜잭션 내에서 실행
- 회원 삭제와 아웃박스 저장이 하나의 단위로 처리되어야 함
- retryEvent: 일반 @Transactional
- 이유: 스케줄러에서 독립적으로 호출
- 재시도와 상태 업데이트가 하나의 트랜잭션으로 처리
- cleanupOldPublishedEvents: 일반 @Transactional
- 이유: 스케줄러에서 독립적으로 호출되는 배치 작업
- getRetryableEvents: readOnly = true (기본값)
- 이유: 조회만 하는 메서드
핵심 포인트
@TransactionalEventListener의 AFTER_COMMIT 단계에서 호출되는 메서드가 데이터베이스 변경 작업을 수행해야 한다면, 반드시 REQUIRES_NEW 전파 속성을 사용하여 새로운 트랜잭션을 시작해야 합니다.
이는 이벤트 리스너가 실행되는 시점에서 기존 트랜잭션이 이미 커밋되어 종료된 상태이기 때문입니다.
결과
이 수정을 통해 아웃박스 패턴이 정상적으로 작동하게 되었고, 메시지 발행과 상태 업데이트가 올바른 순서로 처리되었습니다.

