🧩 문제 상황: 주문과 결제가 같은 트랜잭션에 묶여 있었다
토이 프로젝트를 진행하면서,회원이 상품을 주문하고 결제를 진행하는 기능을 구현했다. 간단한 토이프로젝트 이기때문에 결제는 외부 api를 이용하지않고 간단하게 동작위주로만 구현했기떄문에 하나의 트랜잭션으로 묶었다.
로직 구조는 아래와 같았다 👇
OrderService.createOrder()와 PaymentService.processPayment()가 -> 같은 트랜잭션(@Transactional) 안에서 실행되기 때문에 하나라도 예외가 발생하면 모든 작업이 롤백된다.
@Service
public class OrderService {
    @Autowired
    private PaymentService paymentService;
     
     @Transactional
    public Long createOrder(OrderDto orderDto) {
		....생략
        // 1. 주문 생성
        saveOrder(orderDto);
        // 2. 주문 아이템 저장 및 재고 차감
        saveOrderItemsAndDecreaseStock(orderDto);
        // 3. 결제 처리 (별도 트랜잭션)
        processPayment(orderDto);
        
        ......생략
}
💥 문제 발생
결제 시 회원의 잔액이 부족하면 아래와 같은 예외가 발생했다:
이때 기대했던 동작은 👇
- 주문은 정상적으로 저장 (commit)
 - 해당 상품 제고 감소(commit)
 - 결제만 실패 (rollback)
 
하지만 실제로는 👇
결제 예외 발생 시 주문,재고 까지 롤백되어버렸다. 즉, 주문이 생성되지 않는 문제가 발생했다. 회원은 다시 상품을 담아서 주문해야 하는 일이 생김.
⚙️ 원인 분석
createOrder()와 processPayment()가 같은 트랜잭션에 묶여 있었기 때문.Spring의 기본 전파 속성(Propagation.REQUIRED)은
“이미 트랜잭션이 있으면 합류(join)” 하는 방식이기 때문에 결제에서 예외가 발생하면 주문도 함께 롤백기떄문이였다.
✅ 해결 방법: 트랜잭션 전파 속성 분리
결제 로직을 별도의 트랜잭션으로 분리하기 위해 Propagation.REQUIRES_NEW를 사용했다.
@Service
public class PaymentService {
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void processPayment(OrderDto orderDto) {
        if (user.getBalance() < orderDto.getTotalPrice()) {
            throw new InsufficientBalanceException("잔액이 부족합니다.");
        }
        // 결제 성공 로직
    }
}
이제 동작 흐름은 다음과 같다 👇
- OrderService의 트랜잭션이 시작된다.
 - 주문 정보가 저장된다.
 - PaymentService가 새로운 트랜잭션을 시작한다.
 - 결제 실패 시 결제 트랜잭션만 롤백된다.
 - 주문 트랜잭션은 정상적으로 커밋된다.