Dev./Spring

[Spring] Transactional 사용시 자가호출(Self-Invocation)이슈

limitation01 2025. 12. 8. 13:10

문제

다음과 같은 코드에서,
한 메서드 안에서 다른 @Transactional 메서드를 호출하면
나는 REQUIRES_NEW가 적용되어 별도 트랜잭션이 만들어질 것이라고 기대했다.

그러나 실제 동작은 전혀 달랐다.

@Service
@Slf4j
@RequiredArgsConstructor
public class OrderService {

    private final StockService stockService;
    private final PaymentService paymentService;
    private final PaymentRepository paymentRepository;
    private final OrderRecordService orderRecordService;

    @Transactional
    public void processOrder() {

        // 1. 재고 감소
        stockService.save();
        // 2. 결제 처리
        pamentSave();
        // 3. 주문 기록 s
        orderRecordService.save();

        throw new RuntimeException("예외 발생");

    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void paymentSave() {
        paymentRepository.save(new Payment(1L,"GOOD"));
        log.info("결제 처리 저장 완료");
    }
}

 

왜 REQUIRED_NEW가 동작하지 않았을까?

프록시를 통과한건가 라고 생각해 질문을 했는데 세션에서 프록시 객체가 이미 생성되어있어서라는 답을 들었지만 의미를 한번에 이해하기 힘들었다.

이후 또 한번의 질문을 통해 이 문제의 핵심은 자가 호출 이라는 것을 알게되었다.

자가호출 (Self Invocation)

같은 객체 안에서 자기 자신의 메서드를 직접 호출하는 경우 = 프록시를 다시 거치지 않는 호출
@Transactional
public void processOrder() {
    paymentSave();   //자가 호출
}

@Transactional(propagation = REQUIRES_NEW)
public void paymentSave() {
    생략
}
  • 같은 클래스
  • 자기 자신의 메서드 호출
  • 새로운 호출도 아님
  • 프록시가 아닌 실제 객체 호출

→ 전형적인 Self-invocation

이 때문에 Spring AOP는 다음처럼 판단하게 된다

이 호출은 내가 가로챌 수 있는 종류의 호출이 아니네

왜 자가 호출에서는 @Transactional이 안먹힐까

왜 AOP가 개입하지 못할까 라고도 생각해볼 수 있었다.

Spring의 @Transactional은 AOP 프록시 방식으로 동작한다.

proxy는 입구에만 존재한다.

스프링은 Bean을 만들 때 다음 순서로 생성한다

  1. 호출자
  2. OrderServiceProxy
  3. OrderService 실제 객체

외부에서 서비스 메서드를 호출할 때만 프록시가 개입해 트랜잭션을 시작하거나 종료가 가능하다.

자가 호출은 프록시 외부 호출이 아니다

  • 사진 구조대로 보면 자가 호출시(Object의 되돌아가는 화살표) 프록시를 호출하지 못함 = AOP가 적용될 수 없음
  • 프록시는 외부 호출만 가로챌 수 있는데 이미 프록시 안쪽으로 들어와있다.

⇒ AOP가 개입할 수 없는 호출이기 때문에 @Transactional(propagation = REQUIRES_NEW) 설정이 적용되지 않는다.

배운점

 

  • REQUIRES_NEW가 동작하지 않은 이유는 트랜잭션 옵션이 잘못된 것이 아니라 AOP가 paymentSave() 호출 자체를 볼 수 없었기 때문이다.
  • 자가 호출(self-invocation)은 프록시를 우회한 호출 → AOP 적용 불가 이 구조적 한계 때문에 @Transactional 설정이 무시된다.
  •  processOrder() 내부에서 같은 클래스의 다른 @Transactional 메소드를 호출하면 기대한 트랜잭션 경계가 절대 적용되지 않는다.