💋 문제 상황
아래와 같이 서비스 객체가 있다.
external()
메서드는 트랜잭셔널 어노테이션이 적용되어 있지 않고,
internal()
메서드는 트랜잭셔널 어노테이션이 적용되어 있다.
@Slf4j
@Service
static class CallService {
public void external() {
log.info("call external");
printInfo();
internal();
}
@Transactional
public void internal() {
log.info("call internal");
printInfo();
}
private void printInfo() {
final boolean txActive = TransactionSynchronizationManager.isActualTransactionActive();
log.info("tx active={}", txActive);
}
}
printInfo()
는 트랜잭션과 관련된 설정을 확인하기 위해서 추가했다.
TransactionSynchronizationManager.isActualTransactionActive();
를 호출하면 직접적으로 현재 트랜잭션이 설정된 상태인지를 확인할 수 있다.
@Slf4j
@SpringBootTest
public class InternalCallV1Test {
@Autowired
private CallService callService;
@Test
void printProxy() {
log.info("callService class={}", callService.getClass());
// callService class=class springtx.springtx.apply.InternalCallV1Test$CallService$$EnhancerBySpringCGLIB$$538b5402
}
@Test
void internalCall() {
callService.internal();
// tx active=true
}
@Test
void externalCall() {
callService.external();
// call internal
// tx active=false
}
}
internalCall()
호출 시, 빈으로 등록된 CallService
는 실제 CallService
객체가 아니라, CallService$$EnhancerBySpringCGLIB$$538b5402
의 프록시가 적용된 클래스다.
프록시 객체에서는 internalCall()
를 호출하기 전, 후에 트랜잭션과 관련된 코드들을 AOP를 통해 적용해 놓았다.
externalCall()
를 호출하면 역시 프록시 객체의 externalCall()
를 호출하는데, 이 메서드에는 트랜잭션 어노테이션이 적용되어 있지 않다. 따라서, 그냥 곧바로 실제(target) CallService
객체의 externalCall()
이 호출된다.
그런데 해당 메서드 내부에서 internal()
메서드를 호출하고 있다.
public void external() {
log.info("call external");
printInfo();
internal();
}
이 메서드는 CallService$$EnhancerBySpringCGLIB$$538b5402
가 아니라, 그냥 직접 CallService
의 internal()
메서드를 호출한다. 해당 클래스에 트랜잭션 어노테이션이 붙어 있어서, 트랜잭션이 적용되는 것처럼 보이지만, AOP를 적용한 프록시 객체가 아니기 때문에 실제로는 적용되지 않는다.
💋 문제 해결
InternalService
클래스를 별도로 만들고 internal()
메서드를 옮긴다.
@Slf4j
@Service
class InternalService {
@Transactional
public void internal() {
log.info("call internal");
printTxInfo();
}
private void printTxInfo() {
boolean txActive =
TransactionSynchronizationManager.isActualTransactionActive();
log.info("tx active={}", txActive);
}
}
이렇게 메서드 내부 호출을 외부 호출로 변경하면 해결할 수 있다.
@Slf4j
@RequiredArgsConstructor
@Service
class CallService {
private final InternalService internalService;
public void external() {
log.info("call external");
printTxInfo();
internalService.internal();
}
private void printTxInfo() {
boolean txActive =
TransactionSynchronizationManager.isActualTransactionActive();
log.info("tx active={}", txActive);
}
}
이제 InternalService
를 호출하게 되면, 의존성 주입 받은 프록시 InternalService
객체를 호출하게 된다. 해당 프록시의 internal()
메서드에는 트랜잭션 관련 코드가 있으므로 트랜잭션 프록시가 적용된다.
💋 참고자료
- 김영한 개발자 인프런 강의
도움이 되었다면, 공감/댓글을 달아주면 깃짱에게 큰 힘이 됩니다!🌟
비밀댓글과 메일을 통해 오는 개인적인 질문은 받지 않고 있습니다. 꼭 공개댓글로 남겨주세요!