ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Spring의 @Transactional은 AOP 프록시 기반 프록시를 “통과하는” 메서드 호출만 트랜잭션 어드바이스가 적용된다.
    Spring 2025. 10. 13. 17:12
    728x90
    반응형

    Spring의 @Transactional은 AOP 프록시 기반이라서, 프록시를 “통과하는” 메서드 호출만 트랜잭션 어드바이스가 적용된다는 점입니다. 같은 클래스 내부에서 this.someMethod()로 자기 자신을 호출하면 프록시를 거치지 않으므로, @Transactional이 동작하지 않습니다.

     

    왜 적용 안 되나요? (프록시 구조 이해)

    • 스프링은 @Transactional을 보고 빈의 프록시(Proxy)를 만듭니다. (JDK 동적 프록시나 CGLIB)

    🧩 예시 코드 구조

    @Service
    public class OrderService {
    
        @Transactional
        public void placeOrder(String itemId) {
            System.out.println(">>> 비즈니스 로직 시작");
            saveOrder(itemId); // DB 작업
            System.out.println(">>> 비즈니스 로직 종료");
        }
    
        private void saveOrder(String itemId) {
            System.out.println("주문 저장: " + itemId);
            // 실제 DB insert 로직 (예: orderRepository.save(...))
        }
    }

     

    🧠 스프링이 내부적으로 하는 일

    1. 스프링 컨테이너가 OrderService를 스캔할 때, @Transactional이 붙은 메서드가 있는 것을 감지합니다.
    2. 그 후 원본 객체 대신 프록시 객체를 생성합니다. ( 프록시는 JDK Dynamic Proxy(인터페이스 기반) 또는 CGLIB(클래스 상속 기반) 기술을 이용합니다. )

    예를 들어 OrderService의 실제 빈은 다음과 같이 만들어집니다:

     

    OrderService@Proxy -> 내부적으로 OrderServiceImpl을 감쌈

     

    즉, 컨테이너에는 OrderService 대신 다음과 같은 객체가 들어있어요:

    class OrderService$$SpringCGLIB$$Proxy extends OrderService {
        @Override
        public void placeOrder(String itemId) {
            // 트랜잭션 시작
            TransactionManager.begin();
            try {
                super.placeOrder(itemId); // 실제 비즈니스 로직 호출
                TransactionManager.commit();
            } catch (Exception e) {
                TransactionManager.rollback();
                throw e;
            }
        }
    }

     

    단, @Transactional이 하나도 없으면, 스프링은 프록시를 만들지 않습니다.

     

     

    • 외부에서 OrderService 빈을 호출하면 실제로는 OrderService의 프록시가 먼저 호출됩니다.
    • 프록시는 트랜잭션 어드바이스를 적용한 뒤 실제 타깃(원본 객체)의 메서드를 호출합니다.
    • 그런데 같은 클래스 내부 메서드 호출은 this.someMethod()로 원본 객체 내부에서 바로 일어나므로 프록시를 우회합니다 → 어드바이스 미적용 → @Transactional 무시.

    예시 코드 (이미 프록시를 타고 메소드에 진입하게됨.)

    @Service
    public class OrderService {
    
        // 외부에서 호출되는 진입점 (프록시를 타고 들어옴)
        public void placeOrder(String itemId) {
            // 내부 메서드 호출 - this로 직접 호출 → 프록시 우회
            pay(itemId);  // @Transactional인데도 적용 안 됨
        }
    
        @Transactional
        public void pay(String itemId) {
            System.out.println("TX active? " + TransactionSynchronizationManager.isActualTransactionActive());
            // DB 업데이트...
        }
    }

     

     

    마지막으로.. 

    aop가 우회 된다는 설명 => 만약에 orderservice가 존재하고 order객체,log객체를 저장한다고 치자. 이때 orderservice에 save라는 메소드가 있고 여기서 트랜잭션을 거고 1차적으로 order를 적재함. 그후 logsave라는 메소드에도 request_new라는 트랜잭션을 걸고 save라는 메소드에서 logsave를 호출하면 프록시가 우회되서 logsave에서 에러가 발생하면 , order 객체를 저장하는 save도 롤백됨.

     

    728x90
    반응형
Designed by Tistory.