TIL/김영한의 스프링 DB 2편
트랜잭션 AOP 주의사항 - 프록시 내부 호출
minOE
2025. 3. 2. 03:16
728x90
프록시 내부 호출 문제 예시
package com.example.springtx.apply; import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.TestConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.support.TransactionSynchronizationManager; @Slf4j @SpringBootTest public class InternalCallV1Test { @Autowired CallService callService; @Test void printProxy(){ log.info("callService class={}",callService.getClass()); } @Test void internalCall(){ callService.internal(); } @Test void externalCall(){ callService.external(); } @TestConfiguration static class InternalCallV1TestConfig{ @Bean CallService callService(){ return new CallService(); } } static class CallService{ public void external(){ log.info("call external"); this.printInfo(); this.internal(); // this(생략) 나 자신의 인스턴스 호출 --> 내부 호출은 프록시를 무시함. } @Transactional public void internal(){ log.info("call internal"); printInfo(); } private void printInfo(){ boolean txActive = TransactionSynchronizationManager.isActualTransactionActive(); log.info("tx active={}",txActive); } } } // 프록시 방식의 AOP 한계 //@Transactional을 사용하는 트랙잭션 AOP는 프록시를 사용한다. 프록시를 사용하면 메서드 내부 호출에 프록시를 적용할 수 없다. //해결 - > 내부 호출을 피하기 위해 internall() 메서드를 별도의 클래스로 분리
1) 트랜잭션 정상 작동
@Test void internalCall(){ callService.internal(); }
2) 내부 호출 시 결과@Test void externalCall(){ callService.external(); }
internal 메서드 트랜잭션 무시된 것을 알 수 있음
프록시 내부 호출 문제 해결
package com.example.springtx.apply; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.TestConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.support.TransactionSynchronizationManager; @Slf4j @SpringBootTest public class InternalCallV2Test { @Autowired CallService callService; @Test void printProxy(){ log.info("callService class={}",callService.getClass()); } @Test void internalCall(){ callService.internal(); } @Test void externalCall2(){ callService.external(); } @TestConfiguration static class InternalCallV1TestConfig{ @Bean CallService callService(){ return new CallService(internalService()); } @Bean InternalService internalService(){ return new InternalService(); } } @Slf4j @RequiredArgsConstructor static class CallService{ private final InternalService internalService; public void external(){ log.info("call external"); this.printInfo(); internalService.internal(); // this(생략) 나 자신의 인스턴스 호출 --> 내부 호출은 프록시를 무시함. } @Transactional public void internal(){ log.info("call internal"); printInfo(); } private void printInfo(){ boolean txActive = TransactionSynchronizationManager.isActualTransactionActive(); log.info("tx active={}",txActive); } } static class InternalService{ @Transactional public void internal(){ log.info("call internal"); printInfo(); } private void printInfo(){ boolean txActive = TransactionSynchronizationManager.isActualTransactionActive(); log.info("tx active={}",txActive); } } }
실제 호출되는 흐름
1. 클라이언트인 테스트 코드는 `callService.external()` 을 호출한다.
2.`callService` 는 실제 `callService` 객체 인스턴스이다.
3. `callService` 는 주입 받은 `internalService.internal()` 을 호출한다.
4. `internalService` 는 트랜잭션 프록시이다. `internal()` 메서드에 `@Transactional` 이 붙어 있으므로 트랜잭션 프록시는 트랜잭션을 적용한다.
5. 트랜잭션 적용 후 실제 `internalService` 객체 인스턴스의 `internal()` 을 호출한다.
728x90


