본문 바로가기
Java

@Transactional과 스프링 트랜잭션 AOP

2024. 2. 20.
반응형

스프링에서는 @Transactional 애노테이션을 클래스나 메서드 단위에 붙여 DB 트랜잭션을 적용할 수 있다. 이 애노테이션의 원리와 작동 방식 및 사용 시 주의사항을 알아보자.

 

트랜잭션 적용

트랜잭션 적용 확인하기

  • 선언적 트랜잭션 관리 방식으로 클래스나 메서드에 @Transactional 애노테이션을 붙이면 매우 편리하게 트랜잭션을 적용할 수 있다.
    • 그러나 이 기능을 사용하면 트랜잭션 관련 코드가 눈에 보이지 않고, 심지어 AOP 기반으로 동작하기 때문에 실제로 적용되고 있는지 확인하기 어렵다.
  • 클래스나 메서드에 @Transactional을 사용하면 해당 객체는 트랜잭션 AOP의 적용 대상이 되어 실제 객체 대신 프록시 객체가 스프링 빈에 등록된다.
    • 클라이언트는 실제 객체가 아닌 프록시 객체를 주입받고 프록시 객체의 메서드를 호출하게 되는 것이다.
    • AopUtils.isAopProxy()를 사용하면 현재 객체가 프록시 객체인지 확인할 수 있다.
  • 트랜잭션 AOP 프록시는 호출할 메서드에 @Transactional이 적용되는지 확인한다. 적용된다면 실제 객체의 메서드 호출 전에 트랜잭션을 시작하고, 실제 로직을 호출한 뒤 트랜잭션을 커밋 또는 롤백 후 트랜잭션을 종료한다.
    • 트랜잭션을 적용하지 않는 메서드가 호출될 때는 트랜잭션 관련 로직을 수행하지 않고 단순히 실제 로직을 위임하는 역할을 한다.
    • TransactionSynchronizationManager.isActualTransactionActive()를 사용하면 현재 스레드에 트랜잭션이 적용되어 있는지 확인할 수 있다.

 

트랜잭션 적용 위치

  • 스프링에서는 항상 더 구체적이고 자세한 것이 높은 우선순위를 가진다.
    • 클래스 < 메서드
    • 인터페이스 < 구현체
  • 인터페이스에 @Transactional을 사용하면 AOP를 적용하는 방식에 따라서 AOP가 적용되지 않을 수도 있기 때문에 스프링에서도 권장하지 않는다. 가급적 구체 클래스에 사용하자.

 

트랜잭션 AOP 이용 시 주의사항

프록시 내부 호출

  • 트랜잭션을 적용하기 위해서는 항상 프록시를 통해서 대상 객체를 호출해야 한다.
  • 만약 대상 객체의 내부에서 메서드 호출이 발생하면 프록시를 거치지 않고 대상 객체를 직접 호출하는 문제가 발생한다.
    • → 트랜잭션이 적용되지 않는다!

⚠️ 트랜잭션 AOP는 메서드 내부 호출에 프록시를 적용할 수 없다!

  • 단순한 해결방법은 내부 호출을 피하기 위해 해당 메서드를 별도의 클래스로 분리하는 것이다.

 

접근제어자와 @Transactional

  • 스프링 부트 2.x 이하에서는 public 메서드에만 트랜잭션이 적용된다.
    • 트랜잭션은 주로 비즈니스 로직의 시작점에 걸기 때문에, 트랜잭션이 의도하지 않은 곳까지 과도하게 적용되는 것을 방지하고자 기본적으로 이렇게 설정되어 있다.
  • 스프링 부트 3.x부터는 protectedpackage-visible(default)에도 트랜잭션이 적용된다.
  • 트랜잭션이 적용되지 않는 메서드에 애노테이션을 붙여도 예외가 발생하진 않고 그냥 적용만 무시된다.

 

초기화 시점

  • @Transactional@PostConstruct와 함께 사용할 수 없다.
    • 초기화 코드가 먼저 호출되고, 트랜잭션 AOP가 적용되기 때문에 트랜잭션을 획득할 수 없다.
    • 트랜잭션 AOP 관련 로직이 아닌 곳에만 사용하자.
  • @EventListener(value=ApplicationReadyEvent.class)는 애플리케이션이 완전히 로딩된 후에 호출되므로 초기화에 트랜잭션 AOP를 사용해야 한다면 이 이벤트를 이용하자.

 

 

728x90
반응형

댓글