문제
다음과 같은 코드에서,
한 메서드 안에서 다른 @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을 만들 때 다음 순서로 생성한다
- 호출자
- OrderServiceProxy
- OrderService 실제 객체
외부에서 서비스 메서드를 호출할 때만 프록시가 개입해 트랜잭션을 시작하거나 종료가 가능하다.
자가 호출은 프록시 외부 호출이 아니다

- 사진 구조대로 보면 자가 호출시(Object의 되돌아가는 화살표) 프록시를 호출하지 못함 = AOP가 적용될 수 없음
- 프록시는 외부 호출만 가로챌 수 있는데 이미 프록시 안쪽으로 들어와있다.
⇒ AOP가 개입할 수 없는 호출이기 때문에 @Transactional(propagation = REQUIRES_NEW) 설정이 적용되지 않는다.
배운점
- REQUIRES_NEW가 동작하지 않은 이유는 트랜잭션 옵션이 잘못된 것이 아니라 AOP가 paymentSave() 호출 자체를 볼 수 없었기 때문이다.
- 자가 호출(self-invocation)은 프록시를 우회한 호출 → AOP 적용 불가 이 구조적 한계 때문에 @Transactional 설정이 무시된다.
- processOrder() 내부에서 같은 클래스의 다른 @Transactional 메소드를 호출하면 기대한 트랜잭션 경계가 절대 적용되지 않는다.
'Dev. > Spring' 카테고리의 다른 글
| [Spring] Spring Security + JWT로 로그인 기능 구현 (0) | 2025.12.09 |
|---|---|
| [Spring]왜 Security 의존성을 추가하면 API 호출시 401이 뜰까 (0) | 2025.12.06 |
| [Spring] AOP 정리 (0) | 2025.12.03 |
| [Spring] Interceptor 정리 (1) | 2025.12.02 |
| [Spring] interface의 default 메서드를 언제 써야 할까 (0) | 2025.11.29 |