minOS

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

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