💋 문제 상황
아래와 같이 서비스 객체가 있다.
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() 메서드에는 트랜잭션 관련 코드가 있으므로 트랜잭션 프록시가 적용된다.
💋 참고자료
- 김영한 개발자 인프런 강의

도움이 되었다면, 공감/댓글을 달아주면 깃짱에게 큰 힘이 됩니다!🌟
비밀댓글과 메일을 통해 오는 개인적인 질문은 받지 않고 있습니다. 꼭 공개댓글로 남겨주세요!
'언어+프레임워크 > Spring' 카테고리의 다른 글
| [Spring] 스프링 설정파일에서 @Value로 API KEY 등 변수 가져오기 (0) | 2024.02.08 |
|---|---|
| [Spring/DB] 예외 발생 시 트랜잭션은 commit될까, rollback될까? (0) | 2023.11.11 |
| [Spring/MVC] Dispatcher Servlet: 내장 톰캣 서버, 서블릿 엔진, 동작 과정 (0) | 2023.09.05 |
| [Spring/JPA] Soft Delete: JPA에서 Soft Delete를 구현하는 방법, @SqlDelete, @Where (2) | 2023.08.27 |
| [Spring/JPA] 프록시와 지연 로딩 (9) | 2023.07.31 |