스프링에서는 @Transactional 어노테이션이 붙은 메서드가 진짜 객체에서 호출하는지, 프록시 객체에서 호출하는지 판단해서 로직을 작성하는 것이 중요하다.
아래의 잘못된 테스트 코드를 살펴보고 트랜잭션이 적용되지 않는 원인을 알아보도록 하자.
@SpringBootTest
public class InternalCallV1Test {
@Autowired
CallService callService;
@Test
public void printProxy() throws Exception {
System.out.println("callService = " + callService.getClass());
}
@Test
public void internalCall() throws Exception {
callService.internal();
}
@Test
public void externalCall() throws Exception {
callService.external();
}
@TestConfiguration
static class TestConfig {
@Bean
CallService callService() {
return new CallService();
}
}
static class CallService {
public void external() {
System.out.println("call external!!");
printTxInfo();
internal();
}
@Transactional
public void internal() {
System.out.println("call internal!!");
printTxInfo();
}
private void printTxInfo() {
boolean txActive = TransactionSynchronizationManager.isActualTransactionActive();
System.out.println("txActive = " + txActive);
}
}
}
위 테스트 코드에서 internalCall 메서드를 실행하면 결과는 아래와 같다.

당연히 @Transactional 어노테이션이 붙어 있어 트랜잭션이 활성화가 되어 있다.
이제 externalCall 메서드를 실행시켜보자. 결과는 아래와 같다.

callService의 external() 메서드에는 '@Transactional'이 붙어 있지 않아 트랜잭션이 적용되어 있지 않다.
하지만 callService의 internal() 메서드에는 '@Transactional'이 붙어 있는데도 트랜잭션이 적용되어 있지 않다.
왜 그럴까? 위 테스트 코드를 도식화한 아래의 사진을 보자.

callService 클래스에는 internal() 메서드에 @Transactional 어노테이션이 붙어 있기 때문에 스프링부트가 실행될 때 진짜 객체인 callService가 Bean으로 등록되는 것이 아닌 callService 프록시가 Bean으로 등록이 된다.
참고로 @Transactional 클래스 레벨에 붙던 메서드 레벨에 붙던 상관없이 해당 클래스의 프록시가 스프링 Bean으로 등록된다.
그래서 externalCall() 메서드를 실행하면 callService 프록시가 진짜 객체인 callService의 external() 메서드를 실행한다.
그런데 진짜 객체인 callService의 external() 메서드는 내부에서 internal() 메서드를 호출하고 있다.
이는 this.internal()과 동일한 의미이기 때문에 callService 프록시에서 internal() 메서드를 호출하는 것이 아닌 진짜 객체인 callService에서 internal() 메서드를 호출하는 것과 동일한 의미를 가진다.
따라서 진짜 객체인 callService는 트랜잭션을 처리하는 로직(getConnection, commit, rollback)이 없기 때문에 트랜잭션이 적용되지 않는 것이다. (TransactionSynchronizationManager.isActualTransactionActive() --> false)
다음 포스팅에서는 이를 해결하는 방법을 알아보도록 하자.
참고
김영한 강사님의 스프링 DB 접근 2편의 강의를 듣고 정리한 내용입니다.