[1.28]Transcation aop

Always·2025년 1월 28일

매일메일

목록 보기
28/69

Transaction Proxy

Spring 컨테이너는 빈 생성시 AOP annotaion이 붙은 메서드,클래스가 있을 시 해당 클래스를 프록시로 감싸서, 빈을 대체한다. 즉 스프링 빈에서는 실제 객체를 감싼 프록시가 등록되어서, 실제 작업이 들어올때 프록시가 작업을 가로채서 실제 객체에게 넘겨준다.
그런데 class에서 private으로 선언된 아래와 같은 메서드는 transaction의 적용범위에서 제외된다.

public class Main{
...
@Transcational
private save(){
}
}

Proxy는 크게 인터페이스 기반으로 프록시를 생성하는 JDK-Dynamic Proxy, 구체 클래스를 상속해서 프록시를 생성하는 CGLIB방식이 있다.
일반적으로 현재의 스프링 환경에서는 우리는 인터페이스를 기반으로 서비스를 구현하고, repository도 spring data jpa의 인터페이스를 기반으로 구현되므로, JDK-Dynamic Proxy기반으로 동작한다. 또한 위에서 살펴본 Transaction Proxy역시 JDK-Dynamic Proxy기반이다.
그런데 인터페이스에서는 public 메서드로만 허용가능하므로, 구현체 역시 public으로만 메서드가 구현이 가능하다. 따라서 JDK-Dynamic Proxy에서는 public으로만 프록시가 적용이 가능하다.
하지만 CGLIB방식은 구체 클래스를 상속해서 만들어지므로, protected,public 메서드에 대해서 적용이 가능하다.

내부 메서드 호출시 Transaction 적용안됨.


@Service
public class OrderService {

    @Transactional
    public void outerMethod() {
        System.out.println("Outer method (Transactional)");
        innerMethod();  // 내부 호출
    }

    @Transactional
    public void innerMethod() {
        System.out.println("Inner method (Transactional)");
        // DB 저장 로직
    }
}

위의 코드에서 outerMethod() 를 호출시 innerMethod에는 트랜젝션이 적용될까??
결론적으로 말하면 안된다. 트랜잭션은 위에서 말했다 싶이 프록시 기반의 aop동작하에 동작한다.
그러나 위에서 outerMethod() 내의 innerMethod()는 this.innerMethod();의 의미로 실체 객체로 부터 동작하는 부분이므로, 트랜잭션 프록시가 적용되지 않는다.

해결책?

해결책은 두가지 중 하나이다.

  • 그냥 본인클래스를 주입해주는 것이다.
@Service
@RequiredArgsConstructor
public class OrderService {
    private final OrderService orderService;  // 자기 자신을 주입

    @Transactional
    public void outerMethod() {
        System.out.println("Outer method (Transactional)");
        orderService.innerMethod();  // 내부 호출
    }

    @Transactional
    public void innerMethod() {
        System.out.println("Inner method (Transactional)");
        // DB 저장 로직
    }
}

하지만 스프링 2.6이후부터는 순환참조가 금지 되어있다.

  • 그냥 내부에서 호출하던 메서드를 다른 클래스로 분리해서, 내부 메서드의 실행주체가 프록시에게 있게 한다.
@Service
@RequiredArgsConstructor
public class OrderService {
    private final InnerOrderService innerOrderService;  // 자기 자신을 주입

    @Transactional
    public void outerMethod() {
        System.out.println("Outer method (Transactional)");
        innerOrderService.innerMethod();  // 내부 호출
    }

}


@Service
@RequiredArgsConstructor
public class InnerOrderService {


    @Transactional(propagation = Propagation.REQUIRES_NEW)  
    public void innerMethod() {
        System.out.println("Inner method (Transactional)");
        // DB 저장 로직
    }
}

이렇게 다른 클래스로 분리하면 당연하게도 트랜잭션 프록시 객체로부터 innerMethod가 호출되므로, 트랜잭션이 정상적으로 동작한다.

profile
🐶개발 블로그

0개의 댓글