ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 트랜잭션 AOP 주의사항 - 프록시 내부 호출
    스프링/스프링 기초DB 2023. 4. 1. 14:09
    @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();
            }
        }
    
        @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() 메서드에만 트랜잭션 애노테이션을 넣고 external()메서드에서는 트랜잭션 애노테이션을 넣어주지 않았다. 그리고 printTxInfo() 메서드를 통해서 트랜잭션이 적용되었는지 로그를 출력해주는 테스트 코드이다.

     

    트랜잭션을 사용하면 트랜잭션 프록시를 호출해서 트랜잭션을 관리해준다.

     

    external()을 호출하면 트랙잭션 프록시는 트랜잭션을 적용하지 않고 실제 callService 객체 인스턴스의 external()을 호출한다. 그리고 그 안에 있는 internal() 메서드가 호출되면 그 때 트랜잭션이 사용될 것이라고 예상될 것이다.

     

    하지만 로그를 살펴보면 internal에서도 트랜잭션이 true가 아니라 false라고 로그가 나오게 된다. 트랜잭션 적용이 안되었다는 것이다.

     

     

     

     

     

    프록시와 내부 호출

    사진 : 김영한님 스프링 강의

    여기서 문제점은 external() 메서드가 트랜잭션 프록시에서 external()을 호출한 것이 아니기 때문에 internal() 메서드도 트랜잭션 프록시가 아니라 실제 internal() 메서드가 호출된 것이다.

     

     

     

     

     

     

    해결방안 - internal()메서드의 분리

    static class InternalService { // 클래스의 분리, 프록시가 주입된 것
    
        @Transactional
        public void internal() {
            log.info("call internal");
            printTxInfo();
        }
    
        private void printTxInfo() {
            boolean txActive = TransactionSynchronizationManager.isActualTransactionActive();
            log.info("tx active={}", txActive);
        }
    }

    CallService 클래스의 internal()을 삭제하고, InternalService라는 별도의 클래스를 만들어서 분리시켜주면 해결이 된다.

     

    @TestConfiguration
    static class InternalCallV1TestConfig {
    
        @Bean
        CallService callService() {
            return new CallService(internalService());
        }
    
        @Bean
        InternalService internalService() {
            return new InternalService();
        }
    }

    물론 별도의 클래스를 만들었기 때문에 TestConfiguration에서 별도로 의존관계 주입을 해줘야한다.

    실행을 다시 해본 결과 클래스를 분리해줬을 때에는 internal 메서드는 제대로 프록시가 생겨서 트랜잭션이 성공한 것을 확인할 수 있다.

     

    사진 : 김영한님 스프링 강의

     

     

     

     

    추가적으로 스프링은 public에만 트랜잭션을 적용하기 때문에 private와 protected를 사용하면 트랜잭션 적용이 되지 않는다. 예외도 발생하지 않고 그냥 트랜잭션이 무시가 되기 때문에 public을 사용하도록 해야한다.

Designed by Tistory.