트랜잭션 AOP 주의 사항 - 프록시 내부 호출

wangjh789·2022년 8월 20일
0

[Spring] 스프링-DB-2

목록 보기
13/21

실무에서 많이 만나는 문제

프록시 내부 호출

AOP를 적용하면 스프링은 프록시가 스프링 빈으로 등록이 되기 때문에 의존 관계 주입 시에 항상 실제 객체 대신에 프록시 객체를 주입힌다.
그렇기 때문에 일반적으로 대상 객체를 직접 호출하는 문제는 발생하지 않는다.
하지만 대상 객체 내부에서 메서드 호출이 발생하면 프록시를 거치지 않고 대상 객체를 직접 호출하는 문제가 발생한다.
이런 경우 @Transactional 이 있어도 트랜잭션이 걸리지 않는다.

문제 상황

    @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);
        }
    }

external() 메서드를 호출하면 트랜잭션이 걸리지 않은 상태로 로직을 수행하다 internal()메서드를 호출할 때 트랜잭션을 물고 수행한다 라고 예상하고 짠 로직이다.
하지만 실제론 external()도 internal()도 트랜잭션이 걸리지 않는 문제가 발생한다.

AOP 프록시 객체에서 트랜잭션을 걸지 않은 external()을 호출하면 실제 객체의 external()을 호출하게 되는데 여기서 internal()은 this.internal()로써 실제객체의 internal()을 수행하게 된다.
(트랜잭션을 사용하려면 AOP 프록시 객체를 통해야 한다.)

해결 방안

프록시를 사용하면 메서드 내부 호출에 프록시를 적용할 수 없다는 것이 프록시 방식 AOP 한계이다.
-> internal() 메서드를 별도의 클래스로 분리하자.

    @Slf4j
    @RequiredArgsConstructor
    static class CallService {

        private final InternalService internalService;

        public void internal() {
            internalService.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);
        }
    }

위 코드처럼 클래스를 분리하면 문제가 해결된다. 이 때 @Transactional이 붙은 InternalService의 AOP 프록시가 생성되기 위해선 스프링에서 관리되는 빈 상태여야 한다.

profile
기록

0개의 댓글