티스토리 뷰

트랜잭션 프록시 내부 호출 시 문제점

 클래스 내에 트랜잭션을 적용한 메서드가 있고 그렇지 않은 메서드가 있다. 만약 적용하지 않은 메서드 내에 트랜잭션을 적용한 메서드가 있다면 적용하지 않은 메서드를 실행 시 과연 내부에 있는 적용된 메서드의 트랜잭션이 실행할까? 답은 그렇지 않다. 아래 문제점과 해결 예시를 통해 알아보았다.

 

예제

@Slf4j
@SpringBootTest
public class InternalCallV1Test {

    @Autowired
    CallService callService;
    
    @Test
    void internalCall() {
        callService.internal();
    }
    
    @Test
    void externalCall() {
        callService.external();
    }
    
    @TestConfiguration
    static class InternalCallV1Config {
        @Bean
        CallService callService() {
            return new CallService();
        }
    }
    
    @Slf4j
    static class CallService {
        public void external() {
            log.info("call external");
            printTxInfo();
            internal();
        }
        @Transactional
        public void internal() {
            log.info("call internal");
            printTxInfo();
        }
        // 트랜잭션이 실행 중인지 확인
        private void printTxInfo() {
            boolean txActive =
                    TransactionSynchronizationManager.isActualTransactionActive();
            log.info("tx active={}", txActive);
        }
    }
}

 

internal() 메서드 실행 시

 internal() 메서드를 실행하면 트랜잭션이 실행된 것을 확인할 수 있다.

inernal() 메서드 실행시 그림으로 표현

 

external() 메서드 실행 시

 external() 메서드를 실행하면 internal() 메서드가 트랜잭션이 실행되지 않은 것을 볼 수 있다. 왜냐하면 external() 메서드를 실행 시 프록시를 호출하는 것이 아닌 실제 CallService(프록시x) 인스턴스의 external()이 호출된다. 그리고 external() 내부에서 internal() 메서드를 실행하면 자기 자신의 인스턴스인 this.internal() (프록시x)가 호출된다.

external() 메서드는 프록시 객체가 아닌 인스턴스 메서드가 실행된다.

 

해결

 위에서 봤듯이 트랜잭션을 적용하지 않은 메서드 내에서 트랜잭션을 적용한 메서드를 실행하면 트랜잭션이 실행되지 않았다. 그렇다면 어떻게 해결해야 할까? 바로 internal() 메서드를 분리해서 클래스를 생성해야 한다.

SpringBootTest
public class InternalCallV2Test {
    @Autowired
    CallService callService;
    @Test
    void externalCallV2() {
        callService.external();
    }
    @TestConfiguration
    static class InternalCallV2Config {
        @Bean
        CallService callService() {
            return new CallService(innerService());
        }
        @Bean
        InternalService innerService() {
            return new InternalService();
        }
    }
    @Slf4j
    @RequiredArgsConstructor
    static 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);
        }
    }
    @Slf4j
    static class InternalService {
        @Transactional
        public void internal() {
            log.info("call internal");
            printTxInfo();
        }
        private void printTxInfo() {
            boolean txActive =
                    TransactionSynchronizationManager.isActualTransactionActive();
            log.info("tx active={}", txActive);
        }
    }
}

 위의 코드에서 internal() 메서드를 InternalService 클래스로 분리하였다. 따라서 external() 메서드를 실행하면 CallService의 메서드가 실행되는 것이 아닌 InternalService 프록시 객체가 실행되기 때문에 문제를 해결할 수 있다.

 

정리

 프록시 객체 내에 트랜잭션을 적용한 메서드와 그렇지 않은 메서드가 있을 때 적용하지 않은 메서드 내에 트랜잭션을 적용한 메서드가 있다면 분리를 함으로써 트랜잭션이 적용되지 않은 문제점을 해결할 수 있다.

 


본 포스팅은 “스프링 DB 2편 - 데이터 접근 활용 기술/인프런”를 학습한 내용을 정리한 것

댓글
최근에 올라온 글
«   2026/03   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31
글 보관함
Total
Today
Yesterday