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 |