본문 바로가기
Spring

Spring @TransactionalEventListener AFTER_COMMIT 주의점

by 옹알이옹 2026. 2. 13.
목차

1. @TransactionalEventListener(AFTER_COMMIT) commit 안되는 이슈

2. 문제의 코드 및 로그

3.원인 및 해결 방법

 

 

 1. @TransactionalEventListener(AFTER_COMMIT) commit 안 되는 이슈

 

최근 작업을 하며, 스프링 이벤트를 사용해야 할 상황이 생겼다.

간단히 A모임의 그룹원 모집 종료 처리가 되면 A모임온 가입 신청에 대해  모두 거절 처리를 하는 상태 전이 로직이 있다.

그래서 이때 그룹의 상태 변화(A로직) -> 신청에 대한 상태 변화(B로직) 의 결합도를 낮추고 트랜잭션을 분리하기 위해 사이에 스프링 이벤트를 사용하였고 구체적으로 @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) 를 썼다.

 

문제는 A로직은 잘 변경이 됐는데 B로직은 변경이 안 됐다. 심지어 에러도 안 찍혀서 미친듯한 삽질을 했다.

 

2. 문제의 코드 및 로그

흐름
1. A로직이 이벤트 발행 
2. StudyGroupClosedEventListener가 해당 이벤트 수신

3. RejectAllJoinRequestService의 rejectALL 메서드가 모든 요청에 대해 거절 처리

    rejectALL에@Transactional을 붙여 트랜잭션을 오픈

실행 결과

-  분명 Entity의 상태 값이 바뀌었지만 update 쿼리가 나가지 않음

문제의 로그

기존의 트랜잭션에 참여한다??

 

/**
 * 그룹의 운영 종료 이벤트를 수신하여 가입 신청에 대한 처리를 위한 리스너
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class StudyGroupClosedEventListener {

    private final RejectAllJoinRequestUseCase useCase;

    @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
    public void on(RecruitmentEndEvent event) {

        try {
            useCase.rejectAll(event.getStudyGroupId(),event.getProcessorMemberId());
        } catch (Exception e) {
            log.error("운영 종료에 따른 거절 처리 중 문제 발생");
            log.error(e.getMessage(), e);
        }
    }
}

 

@Service
@RequiredArgsConstructor
public class RejectAllJoinRequestService implements RejectAllJoinRequestUseCase {

    private final JoinRequestRepository joinRequestRepository;
    private final ApplicationEventPublisher eventPublisher;

    @Override
    @Transactional
    public void rejectAll(Long studyGroupId, Long processorMemberId) {

        log.info("txActive={}, syncActive={}, readOnly={}",
                TransactionSynchronizationManager.isActualTransactionActive(),
                TransactionSynchronizationManager.isSynchronizationActive(),
                TransactionSynchronizationManager.isCurrentTransactionReadOnly());

        log.info("txName={}", TransactionSynchronizationManager.getCurrentTransactionName());

        List<JoinRequest> requests =  joinRequestRepository.findAllByStudyGroupIdAndPending(studyGroupId, JoinRequestStatus.PENDING);
        requests.forEach(r ->
                log.info("BEFORE requestId={}, status={}",
                        r.getJoinRequestId(), r.getStatus()));

        requests.forEach(joinRequest-> joinRequest.rejectByEndOperation(processorMemberId));

        joinRequestRepository.saveAll(requests);

        requests.forEach(request -> log.info("[requestId: {} , status: {} ]",request.getJoinRequestId(), request.getStatus()));

        requests.stream()
                .flatMap(request -> request.pullDomainEvents().stream())
                .forEach(eventPublisher::publishEvent);
    }
}

 

 

 

3. 원인 및 해결 방법

 

일반 @EventListener와 다르게 @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) 
은 A로직의 커밋이 완료된 이후 호출이 된다.
그렇기 때문에 난 당연히 커밋이 완료된 이후라면 무조건 트랜잭션이 끝난 후 B로직이 실행될 거라 생각했는데 아니었다.

 

실제로는 커밋이 된 이후에 실행되는 것은 맞지만 트랙잭션 종료에 대한 시점은 보장하지 않았다. ㅜㅜ

결국엔 위에서 rejectALL 메서드에 @Transactional을 붙여 놓았지만 
A의 트랜잭션이 끝나지 않은 상태에서 B가 실행되어 결국 A의 트랜잭션에서 실행된 것이었다.

@Transactional 기본 값 자체가 기존 트랜잭션이 있으면 참여하고, 없으면 만드는 거니까.

 

해결 방법은 아래처럼 속성 값만 바꾸면 된다.(기존 트랜잭션이 있어도 무조건 새로 열라는 속성)

@Transactional을 -> @Transactional(propagation = Propagation.REQUIRES_NEW)


동작 방식을 검색해 보니 spring 공식 문서에 주의하라고 적어놓은 게 있었다.

 

'Spring' 카테고리의 다른 글

Spring 의존 주입 에러 상황  (0) 2026.01.29
Spring REST API @Valid  (0) 2024.03.06
Spring 비지니스 로직 위치  (0) 2024.02.25
Springboot ftp 파일 업로드/다운로드  (0) 2023.08.25
[Spring] CustomReponseEntity  (0) 2023.08.09