본문 바로가기
  • 주니어 개발자의
    RESTful 성장기
Web/Spring

@Transactional

by 돌건 2022. 8. 11.

스프링에서는 @Transactional을 통해 트랜잭션을 관리할 수 있게 해줍니다. @Transactional의 propagation 옵션 값을 지정함으로써 트랜잭션 전파 레벨을 설정할 수 있는데요, 해당 옵션에서 지원하는 트랜잭션 전파 레벨에는 무엇이 있고, 각 전파 레벨은 어떻게 트랜잭션을 진행하는지 알아보겠습니다!

 

📌 @Transactional

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {
    ...
    Propagation propagation() default Propagation.REQUIRED;
}

@Transactional의 선언 부분을 보면, propagation 값을 가지고 있으며, default 값으로는 Propagation.REQUIRED 를 사용하고 있습니다. Propagation은 총 7개의 전파 옵션을 제공하고 있습니다.

public enum Propagation {
    REQUIRED,
    SUPPORTS,
    MANDATORY,
    REQUIRES_NEW,
    NOT_SUPPORTED,
    NEVER,
    NESTED;
}

 

 

📌 Propagation 트랜잭션 정책

Propagation 기존(부모) 트랜잭션 O 기존(부모) 트랜잭션 X
REQUIRED 기존 트랜잭션 참여 트랜잭션 생성
SUPPORTS 기존 트랜잭션 참여 트랜잭션 없이 진행
MANDATORY 기존 트랜잭션 참여 IllegalTransactionStateException
예외 발생
REQUIRES_NEW 트랜잭션 생성
NOT_SUPPORTED 트랜잭션 없이 진행 (기존 트랜잭션은 보류)
NEVER IllegalTransactionStateException 
예외 발생
트랜잭션 없이 진행
NESTED 중첩 트랜잭션 생성 트랜잭션 생성

각 전파 레벨은 기존 트랜잭션 유무에 따라 트랜잭션을 위 표에서와 같이 다르게 진행합니다. 그렇다면 이렇게 진행된 트랜잭션은 언제 커밋되거나 롤백이 되는 걸까요? 아래서 함께 알아보도록 하겠습니다! 😁

 

📌 트랜잭션 commit과 rollback

총 7개의 전파 속성이 존재하지만, 실무에서 주로 사용되는 REQUIREDREQUIRES_NEW에 대해서 알아보도록 하겠습니다.

REQUIRED의 commit과 rollback

REQUIRED의 경우 기존 트랜잭션이 있다면 해당 트랜잭션에 참여한다고 위 표를 통해 알 수 있었습니다. 우선 이해를 돕기 위해 기존 트랜잭션을 외부 Tx, 참여한 트랜잭션을 내부 Tx로 구분하도록 하겠습니다. 

내부 Tx 외부 Tx 결과
COMMIT COMMIT 내부 Tx, 외부 Tx 모두 COMMIT
ROLLBACK COMMIT 내부 Tx, 외부 Tx 모두 ROLLBACK
COMMIT ROLLBACK 내부 Tx, 외부 Tx 모두 ROLLBACK

트랜잭션은 commit/rollback을 하는 순간 종료됩니다. 그렇다면 내부 Tx이 commit/rollback이 되는 순간 트랜잭션이 종료되어야 할텐데요, 외부 Tx와 내부 Tx는 하나의 트랜잭션으로 진행므로 내부 Tx가 commit/rollback이 될 때 실제로 스프링은 어떤 행동도 하지 않기 때문에 외부 Tx의 commit/rollback될 때까지 종료되지 않습니다. 대신, 내부 Tx가 rollback이 되는 순간 내부 Tx가 참여한 트랜잭션은 롤백되어야 한다는 정보를 남기게 됩니다. 외부 Tx가 commit/rollback될 때 비로소 이 정보를 통해 트랜잭션을 rollback하게 되는겁니다..!

이를 흐름 순서대로 정리해보자면 다음과 같습니다.

  1. 외부 Tx 시작, 새로운 트랜잭션 생성
  2. 내부 Tx 시작, 외부 Tx에 참여
  3. 내부 Tx commit or rollback, (생성된 트랜잭션이 아니므로 실제로는 아무 일도 일어나지 않음!)
    1. rollback의 경우, 참여한 트랜잭션이 롤백이 되어야 한다는 정보를 남김 (rollback-only)
  4. 외부 Tx commit or rollback (새로 생성된 트랜잭션이기 때문에 실제로 commit/rollback 진행!)
    1. 내부 Tx가 rollback인 경우, 외부 Tx의 상태는 rollback-only이므로 외부 Tx가 commit이 되더라도 전체가 rollback이 되고, UnexpectedRollbackException 예외 발생.

REQUIRES_NEW의 commit과 rollback

REQUIRES_NEW의 경우 기존 트랜잭션의 유무와 상관 없이 항상 새로운 트랜잭션이 생성해 진행합니다. 그렇기 때문에 각각의 트랜잭션이 진행되고, 결국 커밋과 롤백도 각각 진행됩니다. 즉, 내부 Tx의 rollback 여부가 외부 Tx에 영향을 주지 않습니다. 

  1. 외부 Tx 시작 - 신규 트랜잭션 생성
  2. 내부 Tx 시작 - 신규 트랜잭션 생성, 외부 Tx 보류
  3. 내부 Tx commit/rollback - 신규 트랜잭션이기 때문에 실제로 commit/rollback 진행
  4. 외부 Tx commit/rollback - 신규 트랜잭션이기 때문에 실제로 commit/rollback 진행 

❗️주의사항❗️

트랜잭션은 동일한 DB 커넥션을 이용하게 됩니다. 즉, REQUIRES_NEW 옵션을 사용하게 되는 경우에는 외부 Tx, 내부 Tx 각각 다른 DB 커넥션을 사용하게 됩니다. 게다가 내부 Tx가 종료되기 전까지 외부 Tx는 보류되기 때문에 DB 커넥션이 총 2개(트랜잭션 수만큼) 사용됩니다. 그렇기 때문에 복잡하고 오래 걸리는 트랜잭션을 진행해야 할 때는 REQUIRES_NEW의 사용을 지양하는 것이 자원의 비효율적 사용을 예방할 수 있을 것입니다!

 

지금까지 스프링이 제공하는 @Transactional의 propagation 옵션에 따른 트랜잭션 진행 방법에 대해 간략하게 알아보았습니다. 감사합니다! 😁

댓글