@Transactional 내부 호출 문제와 해결 방법

2025. 3. 12. 13:50·Spring

Spring에서 @Transactional을 사용할 때, 같은 클래스 내에서 트랜잭션 메서드를 직접 호출하면 트랜잭션이 적용되지 않는 문제가 발생합니다.
이번 글에서는 이 문제의 원인과 해결 방법을 알아보겠습니다.

 

@Transactional 내부 호출 문제란?

- Spring의 @Transactional은 프록시(Proxy) 기반의 AOP(Aspect-Oriented Programming) 방식으로 동작합니다.
즉, 스프링이 생성한 프록시 객체를 통해 @Transactional이 적용된 메서드가 호출될 때만 트랜잭션이 활성화됩니다.

@Service
public class OrderService {

    public void nonTxMethod() {
        log.info("call nonTxMethod");
        // 트랜잭션이 활성화 되어있는지 확인
        log.info("Tx Active={}", TransactionSynchronizationManager.isActualTransactionActive());
        txMethod();
    }
    
    @Transactional
    public void txMethod() {
        log.info("call txMethod");
        // 트랜잭션이 활성화 되어있는지 확인
        log.info("Tx Active={}", TransactionSynchronizationManager.isActualTransactionActive());
    }
}

위의 코드를 실행해보면 아래의 로그가 출력이 됩니다. 

call nonTxMethod
txActive=false
call txMethod
txActive=false

 

내부 호출 시 트랜잭션이 적용되지 않는 이유

  • Spring은 @Transactional이 적용된 객체를 감싸는 프록시 객체를 생성하여 스프링 컨테이너에 등록합니다.
  • 프록시 객체를 통해 트랜잭션이 시작된 후, 실제 대상 객체(Target)의 메서드를 실행하는 방식으로 동작합니다.
  • 하지만 같은 클래스 내에서 this.메서드()로 호출하면 프록시를 거치지 않고, 원본 객체의 메서드가 실행되므로 트랜잭션이 적용되지 않습니다.

 

아래는 Spring의 CGLIB 프록시 객체가 생성된다고 가정했을 때의 구조입니다.

public class OrderServiceProxy extends OrderService {
    private final TransactionManager transactionManager;
    private final OrderService target; // 실제 대상 객체 (OrderService)

    public OrderServiceProxy(TransactionManager transactionManager, OrderService target) {
        this.transactionManager = transactionManager;
        this.target = target; // 원본 객체 주입
    }

    // 트랜잭션이 없는 메서드 호출 (그냥 원본 객체 호출)
    @Override
    public void nonTxMethod() {
        target.nonTxMethod(); // 트랜잭션 없이 원본 객체의 메서드 실행
    }

    // 트랜잭션이 적용된 메서드 호출
    @Override
    public void txMethod() {
        try {
            transactionManager.begin(); // 트랜잭션 시작
            target.txMethod(); // 원본 객체의 txMethod() 실행
            transactionManager.commit(); // 성공 시 커밋
        } catch (Exception e) {
            transactionManager.rollback(); // 예외 발생 시 롤백
        }
    }
}

 

실행 흐름

1. OrderService.nonTxMethod(); 실행

2. nonTxMethod() 내부에서 txMethod()를 직접 호출 (this.txMethod())
3. txMethod()가 프록시를 거치지 않고 원본 객체의 메서드가 실행됨
4. 트랜잭션이 활성화되지 않음

 

 

해결 방법: @Transactional 메서드를 별도 클래스로 분리

 

- 트랜잭션 처리 클래스 생성

@Service
public class OrderTransactionalService {

    @Transactional
    public void txMethod() {
        System.out.println("✅ 트랜잭션 시작됨");
    }
}

 

 

- 기존 서비스에서 분리된 클래스를 주입받아 사용

@Service
public class OrderService {

    private final OrderTransactionalService orderTransactionalService;

    public OrderService(OrderTransactionalService orderTransactionalService) {
        this.orderTransactionalService = orderTransactionalService;
    }

    public void nonTxMethod() {
        System.out.println("트랜잭션이 없는 메서드 실행");
        orderTransactionalService.txMethod(); // 트랜잭션 적용됨
    }
}

 

 

결론

- 내부 호출 시 트랜잭션이 적용되지 않는 이유는 프록시를 거치지 않기 때문
- 트랜잭션이 필요한 메서드를 별도의 클래스(OrderTransactionalService)로 분리하면 문제 해결 가능
- Spring 컨테이너에서 관리하는 다른 @Service 빈을 주입받아 호출하면 트랜잭션이 정상 적용됨

- 이제 트랜잭션 내부 호출 문제를 완벽하게 해결하고, 유지보수하기 쉬운 구조로 개선할 수 있습니다

'Spring' 카테고리의 다른 글

ApplicationEvent로 비즈니스 경계 나누기: 고민과 선택  (3) 2025.08.28
결제 시스템에 Circuit Breaker를 도입하며 – 계층별 책임 분리와 안정성 확보 전략  (1) 2025.08.22
DB 인덱스 설계와 Redis 캐싱(Port-Adapter) 적용기  (2) 2025.08.15
Spring 트랜잭션 핵심 정리  (2) 2025.08.08
도메인 검증 책임은 어디에 둘까요? – Service vs Facade, 저는 이렇게 선택했습니다  (2) 2025.08.01
'Spring' 카테고리의 다른 글
  • 결제 시스템에 Circuit Breaker를 도입하며 – 계층별 책임 분리와 안정성 확보 전략
  • DB 인덱스 설계와 Redis 캐싱(Port-Adapter) 적용기
  • Spring 트랜잭션 핵심 정리
  • 도메인 검증 책임은 어디에 둘까요? – Service vs Facade, 저는 이렇게 선택했습니다
JoshDev
JoshDev
    • 분류 전체보기 (24)
      • Java (3)
      • Spring (9)
      • Test Code (2)
      • WIL (6)
      • Vue.js (2)
      • WEB (0)
      • DB (1)
        • MySQL (1)
  • 인기 글

  • hELLO· Designed By정상우.v4.10.4
JoshDev
@Transactional 내부 호출 문제와 해결 방법
상단으로

티스토리툴바