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 |