티스토리 뷰
클래스에 @Transactional을 붙이면 모든 메서드에 트랜잭션이 적용된다. 특정 메서드에 @Transactional을 붙이면 해당 메서드에만 트랜잭션이 적용된다.
그런데 의문이 들었다. @Transactional이 붙지 않은 메서드를 호출할 때, 해당 클래스는 AOP 프록시가 적용되어 있는지에 대해 궁금했다.
결론부터 말하자면 AOP 프록시가 적용된 상태에서 메서드를 호출하게 된다. 물론 @Transactional이 붙지 않았기 때문에 해당 메서드는 트랜잭션이 적용되지 않는다.
트랜잭션 AOP 적용
CallService 클래스를 보면 internal 메서드에 @Transactional이 붙었다. 따라서 해당 클래스는 AOP 프록시가 적용된다. AOP 프록시가 적용된다는 의미는 프록시 객체가 트랜잭션을 시작하고 실제 객체를 호출한 뒤, 트랜잭션을 종료함을 말한다.
static class CallService {
public void external() {
log.info("call external");
printTx();
}
@Transactional
public void internal() {
log.info("call internal");
printTx();
}
private void printTx() {
boolean txActive = TransactionSynchronizationManager.isActualTransactionActive();
log.info("tx active={}", txActive);
}
}
external() 메서드 호출
@Transactional이 붙지 않은 external() 메서드를 호출하면 다음과 같은 결과를 얻는다.
실제 객체가 스프링 빈으로 등록되지 않고 프록시 AOP가 적용된 객체가 스프링 빈으로 등록된다. 또한 트랜잭션이 적용되었는지에 대해 false를 반환하기 때문에 트랜잭션이 적용되지 않았음을 알 수 있다.
internal() 메서드 호출
@Transactional이 붙은 internal() 메서드를 호출하면 다음과 같은 결과를 얻는다.
당연히 AOP 프록시가 적용된 객체가 스프링 빈으로 등록되고, 트랜잭션 적용 여부를 보면 true를 반환한다.
그렇다면 @Transactional이 붙지 않은 external() 메서드에 @Transactional이 붙은 internal() 메서드를 내부 호출하면 어떻게 될까? @Transactional이 붙은 internal() 메서드는 트랜잭션이 적용될 것 같지만 아쉽게도 그렇지 않다.
external() 메서드 수정
external() 메서드 내부에서 internal() 메서드를 호출하는 코드가 추가되었다.
static class CallService {
public void external() {
log.info("call external");
printTx();
internal();
}
@Transactional
public void internal() {
log.info("call internal");
printTx();
}
private void printTx() {
boolean txActive = TransactionSynchronizationManager.isActualTransactionActive();
log.info("tx active={}", txActive);
}
}
external() 메서드 호출
external() 메서드를 호출하면 다음 결과를 얻는다.
위에서 단독으로 internal()을 호출했을 때는 트랜잭션 적용 여부가 true였는데 이번에는 false가 반환되었다. 그 이유는 다음과 같다.
AOP 프록시가 적용된 CallService 클래스의 external() 메서드 호출 -> external() 메서드는 @Transactional이 붙지 않았기 때문에 트랜잭션을 적용하지 않음 -> 따라서 실제 CallService 객체의 external() 메서드를 호출 -> internal() 메서드 내부 호출
핵심 포인트는 실제 CallService 객체를 호출한다는 것이다. 트랜잭션이 적용되지 않는 객체에서 internal() 메서드를 호출하였으니 트랜잭션이 적용될 수가 없다.
위에서 단독으로 internal() 메서드를 호출할 당시에는 트랜잭션이 적용된 객체를 호출하기 때문에 트랜잭션이 정상적으로 적용된 것이다.
내부 호출을 클래스로 분리
위에서 트랜잭션이 적용되지 않은 이유는 트랜잭션이 적용되지 않는 객체에서 내부 호출이 일어났기 때문이었다. 이러한 문제를 해결하기 위해서는 내부 호출 메서드를 클래스로 분리하는 방법이 있다.
internal() 메서드 -> 클래스로 분리
static class InternalService {
@Transactional
public void internal() {
log.info("call internal");
printTx();
}
private void printTx() {
log.info("tx active={}", TransactionSynchronizationManager.isActualTransactionActive());
}
}
external() 메서드 호출
static class CallService {
private final InternalService internalService;
@Autowired
public CallService(InternalService internalService) {
this.internalService = internalService;
}
public void external() {
log.info("call external");
log.info("external class={}", this.getClass());
printTx();
log.info("internal class={}", internalService.getClass());
internalService.internal();
}
private void printTx() {
log.info("tx active={}", TransactionSynchronizationManager.isActualTransactionActive());
}
}
결과를 보면 @Transactioanl이 붙지 않은 external() 메서드는 트랜잭션 AOP가 적용되지 않은 실제 객체를 호출하고 있고, internal() 메서드의 경우에는 @Transactional이 붙었기 때문에 트랜잭션 AOP가 적용된 객체를 호출하고 있음을 알 수 있다.
또한 internal() 메서드를 클래스로 분리함으로써 트랜잭션도 정상적으로 적용되고 있음을 알 수 있다.
결론
트랜잭션이 적용되지 않은 객체에서 내부 호출을 하면 @Transactional이 붙어있을지라도 트랜잭션이 적용되지 않는다.
이러한 문제를 해결하기 위해서는 내부 호출 메서드를 클래스로 분리하여 호출하면 된다.