@Transactional 주의점

HoyongLee·2023년 3월 13일
0

@Transactional은 Spring AOP로 동작한다. Spring AOP는 프록시 패턴을 기반으로 동작하게 되는데 프록시 객체를 자동 생성하는 방법으로 JdkDynamicProxy, CGLIB을 사용한다.

  • JdkDynamicProxy
    인터페이스를 기반으로 프록시를 자동생성한다.
    내부적으로 리플렉션을 사용하여 프록시를 생성하기 때문에 오버헤드가 크다.
  • CGLIB
    상속을 기반으로 바이트코드를 조작하여 프록시를 자동생성한다.
    (내부적으로 리플렉션을 사용하긴 하는데, 최적화 방법을 사용해서 리플렉션의 성능 문제를 최소화하여 성능이 좋아 Spring AOP의 기본 프록시 객체 생성으로 사용한다고 한다)

@Transactional이 붙은 메서드, 클래스가 호출이 되면, 자동으로 생성된 프록시에서 트랜잭션을 열고 호출이 종료되면 트랜잭션을 닫는다. 또한, 예외가 발생하면 롤백까지 시켜준다.
(그림 출처 : https://www.slideshare.net/rohitsghatol/aspect-oriented-prog-with-aspectj-spring-aop)

public class Proxy extends TargetClass {
	TargetClass target;
    
    @Override
    public void doSomething() {
    	transaction_begin();
        target.doSomething();
        transaction_end();
    }
}

위와 같은 코드가 Spring AOP를 통해 생성된다(이해를 돕기 위한 예시이다.).

@Service
public class TxService {

  public void callSomething() {
    String currentTransactionName = TransactionSynchronizationManager.getCurrentTransactionName();
    System.out.println("callSomething : " + currentTransactionName);
    doSomething();
  }

  @Transactional
  public void doSomething() {
    String currentTransactionName = TransactionSynchronizationManager.getCurrentTransactionName();
    System.out.println("doSomething : " + currentTransactionName);
  }
}

------테스트 코드-------
@SpringBootTest
class TxTest {

  @Autowired
  TxService txService;
  
  void test() {
	txService.callSomething();
  }
}

위 코드는 트랜잭션이 동작할까???

callSomething : null
doSomething : null

현재 트랜잭션의 이름을 출력한 결과, 동작하지 않는다.

왜 ???
callSomething()@Transactional이 붙지 않은 메서드이기 때문에, TxService 인스턴스의 메서드가 호출된다. 그리고 내부에서 doSomething() 메서드를 호출하게 되는데, 자바에서는 메서드 앞에 아무것도 붙어 있지 않으면 this를 붙여 호출한다. 따라서 프록시 객체를 타지 않고 바로 TxService 인스턴스의 메서드가 호출되기 때문에 트랜잭션이 동작하지 않는 것이다.

@Service
public class TxService {
  @Autowired
  TxServiceCalled txServiceCalled;
  
  public void callSomething() {
    String currentTransactionName = TransactionSynchronizationManager.getCurrentTransactionName();
    System.out.println("callSomething : " + currentTransactionName);
    txServiceCalled.doSomething();
  }
}

----------------------------------------------------

@Service
public class TxServiceCalled {

  @Transactional
  public void doSomething() {
    String currentTransactionName = TransactionSynchronizationManager.getCurrentTransactionName();
    System.out.println("doSomething : " + currentTransactionName);
  }
}

-----------테스트 결과-------------
callSomething : null
doSomething : com.example.community.service.TxServiceCalled.doSomething

위 코드처럼 doSomething()이 다른 Bean에 존재한다면 트랜잭션이 동작한다. TxService의 callSomething() 내부에서 다른 Bean의 메서드를 호출하므로 프록시 객체가 만들어져 트랜잭션을 타게 된다.

주의사항

  • @Transactional이 붙은 메서드를 @Transactional이 붙어있지 않은 같은 클래스의 메서드에서 호출하게 되면, 프록시를 거치지 않아 트랜잭션을 타지 않는다.
  • 프록시 객체는 인터페이스를 구현하거나 상속을 통해 생성되고 원본 메서드를 오버라이딩하여 부가 기능 혹은 접근 제어를 수행하므로 public 메서드여야한다.

참고자료

  • chatGPT
  • 인프런 - 김영한 님의 스프링 강의
profile
아직 반지하

0개의 댓글

관련 채용 정보