1. 문제 코드

상황: 판매자 승격 이벤트를 AFTER_COMMIT에서 처리하는데, DB에는 Role 변경이 반영되지 않거나(마이페이지에 계속 USER), 토큰도 갱신되지 않는 문제가 발생.

@Component
@RequiredArgsConstructor
public class AuthEventListener {

    private final AuthPromoteSellerUseCase authPromoteSellerUseCase;

    @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
    public void handleSellerPromotedEvent(SellerPromotedEvent event) {
        // 1) 원래 트랜잭션이 커밋된 이후에 실행됨
        // 2) 여기서 DB 변경이 필요하면 별도의 트랜잭션이 필요함
        authPromoteSellerUseCase.promoteSeller(event.memberPublicId());
    }
}
@Service
@RequiredArgsConstructor
public class AuthPromoteSellerUseCase {

    private final AccountRepository accountRepository;

    @Transactional // (문제) 기본 REQUIRED
    public void promoteSeller(String memberPublicId) {
        Account account = accountRepository.findByMemberPublicId(memberPublicId)
                .orElseThrow(() -> new CustomException(ErrorType.MEMBER_NOT_FOUND));

        // Role 변경
        account.changeRole(AuthRole.SELLER);

        // 저장/커밋이 보장되지 않아 실제 DB 반영이 누락될 수 있음
        // (AFTER_COMMIT 시점에는 기존 트랜잭션이 이미 종료)
    }
}

2. 문제 원인

AFTER_COMMIT 이벤트 리스너에서 실행되는 DB 변경 로직은 기존 트랜잭션이 이미 종료된 상태라 커밋이 보장되지 않음

  1. AFTER_COMMIT 단계 특징 @TransactionalEventListener(phase = AFTER_COMMIT)는 원래 트랜잭션이 커밋된 후 이벤트 핸들러를 실행한다. 즉, 이벤트 핸들러가 실행될 때는 기존 트랜잭션이 이미 끝난 상태다.
  2. DB 변경은 트랜잭션 커밋 시점에 확정됨 JPA 엔티티 변경(account.changeRole(...))은 보통 트랜잭션 커밋 시 flush → DB 반영이 일어난다. 그런데 AFTER_COMMIT에서 호출되는 로직이 독립 트랜잭션을 확실히 열지 않으면, 변경이 커밋되지 않아 DB에 반영이 누락될 수 있다.
  3. 증상으로 나타나는 것들

3. 해결 방법

Role 변경 로직을 반드시 독립 트랜잭션에서 실행하도록 보장

핵심은 이벤트 리스너(AFTER_COMMIT)에서 호출되더라도 무조건 새 트랜잭션을 열어 커밋까지 가게 만드는 것.

3-1. UseCase에 REQUIRES_NEW 적용

@Service
@RequiredArgsConstructor
public class AuthPromoteSellerUseCase {

    private final AccountRepository accountRepository;

    // 변경 전: @Transactional (REQUIRED)
    // 변경 후:
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void promoteSeller(String memberPublicId) {
        Account account = accountRepository.findByMemberPublicId(memberPublicId)
                .orElseThrow(() -> new CustomException(ErrorType.MEMBER_NOT_FOUND));

        account.changeRole(AuthRole.SELLER);
        // 이 메서드 종료 시점에 새 트랜잭션이 커밋되며 DB에 확정 반영됨
    }
}

REQUIRES_NEW 효과