Spring @Transactional의 Propagation

라모스·2023년 5월 17일
0
post-thumbnail

Spring의 @Transactional과 propagation에 대해 알아보자.

(추 후 propagation 관련 예제 코드 추가 예정입니다)

Spring Transaction 이해

  • @Transactional 사용 시 Proxy 기반으로 동작하기 때문에 내부 호출에 주의해야 한다.
    • Proxy 대상 객체의 내부에서 메서드 호출이 발생하면 Proxy를 거치지 않고 대상 객체를 직접 호출하는 문제가 발생하는데, 이 경우 애노테이션이 있어도 트랜잭션이 적용되지 않는다.
    • external class에 @Transactional 이 걸려있지 않고 메소드를 호출 시 해당 메소드에서 @Transactional 이 걸려있는 호출 대상 메소드는 결론적으로 트랜잭션이 적용되지 않는다는 말임.
    • 별도의 클래스로 분리하는 방법을 주로 사용하자.
  • Runtime Exception은 항상 롤백된다. Checked Exception의 경우 rollbackFor 옵션을 사용해서 비즈니스 상황에 따라 커밋과 롤백을 선택하면 된다.

@Transactional

스프링에서 트랜잭션 처리는 보통 @Transactional 어노테이션을 많이 사용하게 된다.
@Transactional은 클래스 또는 메소드에 사용할 수 있으며 @Transactional이 포함된 메소드가 호출될 경우, 프록시 객체가 생성된다.

프록시 객체는 해당 메소드 실행 이전에 PlatformTransactionManager를 사용하여 트랜잭션을 시작하고 결과에 따라 Commit 또는 Rollback 한다.

Commit과 Rollback은 어느 경우에 발생하는가?

  • CheckedException 발생 혹은 예외가 없을 땐 Commit
  • UncheckedException이 발생하면 Rollback

@Transactional 우선순위는 어떻게 되는가?

@Transactional은 다음과 같은 우선 순위를 가진다.

  1. 클래스 메소드
  2. 클래스
  3. 인터페이스 메소드
  4. 인터페이스

단, 주의해야 할 부분이 있다.

@Transactional 어노테이션 같은 경우 Spring AOP를 이용하게 되는데 이 AOP는 기본적으로 Dynamic Proxy를 이용한다. Dynamic Proxy는 인터페이스 기반으로 동작하기 때문에 인터페이스가 없을경우 트랜잭션이 동작하지 않는다.

인터페이스 없이 트랜잭션 동작하게 하려면 CGLib(Code Generation Library) Proxy를 이용하면 된다. CGLib Proxy는 클래스에 대한 Proxy가 가능하기 때문에 인터페이스가 없어도 된다.

📌 주의 사항

  • @Transactional은 public method에만 적용된다.
    @Transactional은 프록시 기반으로 동작하기 때문에 public method가 아니면 동작하지 않는다.
    만약 private method에 @Transactional을 적용한다고 하면 IDE에서 컴파일 오류를 확인할 수 있다.
  • 동일한 클래스 내의 메소드 호출은 @Transactional이 동작하지 않는다.
    프록시 기반으로 동작하기 때문에 외부에서 접근할 때 AOP를 통해서 프록시 객체를 접근할 수 있다. 그러나 클래스내에서 다른 메소드를 호출하게 되면 프록시로 접근하지 않고 직접 접근하기 때문에 메소드에 선언해 놓은 @Transactional 이 정상적으로 동작하지 않을 것이다.

클래스 내 메소드를 호출하여 @Transactional을 동작하게 하고 싶다면 다음 방법을 고려해보자.

  1. 새로운 클래스를 만들어 메소드를 위임한다.
  2. aspectj를 사용한다.
@SpringBootApplication
@EnableTransactionManagement(proxyTargetClass = true, mode = AdviceMode.ASPECTJ)
@EnableLoadTimeWeaving(aspectjWeaving = EnableLoadTimeWeaving.AspectJWeaving.ENABLED)
public class TestApplication {
    public static void main(String[] args) {
        SpringApplication.run(TestApplication.class, args);
    }
}

Spring Transaction Propagation

(활용 방안에 관련하여 현재 수정 중)

  • REQUIRED 가 default
  • 외부 트랜잭션 → 내부 트랜잭션 인 경우 이를 하나의 물리 트랜잭션으로 묶고 두 트랜잭션 각각은 논리 트랜잭션이라 구분한다.

논리 트랜잭션의 개념을 도입하여 다음 두 원칙을 생각하자.

  • 모든 논리 트랜잭션이 커밋되어야 물리 트랜잭션이 커밋된다.
  • 하나의 논리 트랜잭션이라도 롤백되면 물리 트랜잭션은 롤백된다.

외부 롤백이 먼저냐 내부 롤백이 먼저냐의 순서에 따라 전체 트랜잭션(물리 트랜잭션)의 롤백이 어떻게 달라지는지 확인해봐야 한다. 이는 전파 옵션에 따라서도 다르다. 실무에서 대부분은 default 값을 사용하고 아주 가끔 REQUIRES_NEW 를 사용하며 나머지는 거의 사용하지 않는다.

REQUIRED

기존 트랜잭션이 없으면 생성, 있으면 참여한다. 트랜잭션이 필수라는 의미로 이해하자.

REQUIRES_NEW

항상 새로운 트랜잭션을 생성한다.

SUPPORT

트랜잭션을 지원한다. 기존 트랜잭션이 없으면 없는대로 진행하고 있으면 참여한다.

NOT_SUPPORT

트랜잭션 지원 x

MANDATORY

의무사항이다. 반드시 트랜잭션이 있어야 한다.
기존 트랜잭션이 없는 경우 IllegalTransactionStateException 발생

NEVER

트랜잭션을 사용하지 않는다는 의미다. 기존 트랜잭션이 있으면 예외 발생.

NESTED

  • 기존 트랜잭션 없음: 새로운 트랜잭션 생성.
  • 기존 트랜잭션 있음: 중첩 트랜잭션을 만든다.
    • 중첩 트랜잭션은 외부 트랜잭션의 영향을 받지만, 중첩 트랜잭션은 외부에 영향을 주지 않는다.
    • 중첩 트랜잭션이 롤백되어도 외부 트랜잭션은 커밋할 수 있다.
    • 외부 트랜잭션이 롤백 되면 중첩 트랜잭션도 함께 롤백된다.
    • 중첩 트랜잭션은 JPA에서 사용 불가

정리

  • isolation, timeout, readOnly는 트랜잭션이 처음 시작될 때만 적용 o
  • 트랜잭션에 참여하는 경우에는 적용 x

References

profile
Step by step goes a long way.

0개의 댓글