Spring AOP 사용 시 주의점 (feat. @Transactional, @Cacheable, ...)

김형진·2023년 7월 31일
0

문제 상황

어느날 실무에서 발견한 문제이다.
event 발행 혹은 옵저버에게 notify하는 로직이 핵심로직과 함께 섞여 트랜잭션처리되고 있었는데,
이벤트 발행하거나 옵저버에게 알림을 주는 과정에서 에러가 발생하여 핵심로직까지 롤백될 필요가 없었기 때문에 이를 분리하는 작업을 진행해야 했다.


@Transactional
public void somethingMethod(){

	// 트랜잭션이 함께 묶여야 하는 로직들
    logic1();
    logic2();
    logic3();
    
    // 트랜잭션에 함께 묶이면 안되는 로직
    publishEvent(); // 로직 처리에 대한 이벤트 발행
    notify();        // 해당 class를 옵저빙하는 옵저버들에게 알림

}

당시에는 큰 고민 없이 아래와 같이 메소드 간 트랜잭션을 분리하여 핵심로직에만 트랜잭션을 걸도록 수정하였다.


public void somethingMethod(){
	coreLogics();
    additionalLogics();
}

@Transactional
public void coreLogics(){

	// 트랜잭션이 함께 묶여야 하는 로직들
    logic1();
    logic2();
    logic3();
   
}

public void additionalLogics(){
    publishEvent(); 
    notify(); 
}

트랜잭션이 함께 묶여야 하는 로직들은 하나의 단위 메소드로 분리하고 트랜잭션을 따로 걸어주었고 이는 트랜잭션이 잘 작동할 것 처럼 보였다.



함정

그러나 스프링의 @Transactional의 작동 방식을 알면 위와 같은 상황에서 문제를 알아챘어야 했다.

@Transactional 어노테이션은 런타임 시점에 해당 어노테이션이 걸린 객체에 대한 proxy를 생성한다.
그리고 프록시는 진짜 객체에 업무를 위임하기 전 후로 트랜잭션에 대한 처리를 진행한다.

우리가 주입받는 빈은 실제 객체의 인스턴스가 아닌 실제 객체 인스턴스를 품고 있는 프록시 객체인데, 이를 통해 외부에서 클래스의 메소드를 호출할 때 무조건 proxy를 거치게 된다.

그러나, 같은 클래스 내의 method를 호출하면 proxy를 거치지 않는다.

즉 somethingMethod 메소드가 coreLogics를 호출하는 것은 proxy를 통해서가 아닌, 직접 호출하는 것이기 때문에 어떠한 트랜잭션 처리도 이뤄지지 않고 있는 것이다.

이를 해결하기 위해선 somethingMethod 를 호출하는 client와 서비스 사이에 계층을 두어 트랜잭션 처리가 걸린 프록시를 통해서만 코어 로직을 호출하는 방법 등을 생각해보아야 할 것이다.

profile
히히

0개의 댓글