애플리케이션을 개발할 때 데이터의 일관성을 유지하고 안정적인 동작을 보호하기 위해 트랜잭션 관리가 필수적입니다.

트랜잭션은 데이터를 처리하는 동안 발생할 수 있는 오류나 시스템 장애에 대비해 작업을 롤백(rollback)하거나 커밋(commit)하는 중요한 역할을 합니다. 트랜잭션 전파(Transaction Propagation)는 여러 개의 트랜잭션이 서로 어떻게 상호작용하고, 하나의 트랜잭션이 다른 트랜잭션에게 어떻게 영향을 미칠지 결정하는 중요한 개념입니다.

이번 포스팅에서는 트랜잭션 전파에서 각 속성들의 개념과 동작과정에 대해 알아보도록 하겠습니다.

 

트랜잭션 전파 속성(Transaction Propagation)이란?

Spring이 제공하는 선언적 트랜잭션(@Transactional)은 여러 트랜잭션을 묶어 하나의 큰 트랜잭션 경계를 설정할 수 있는 장점이 있습니다.

여기서 트랜잭션 전파 속성(Propagation)은 이미 트랜잭션이 진행 중일 때, 새로운 트랜잭션이 어떻게 처리될지를 결정하는 중요한 개념입니다.

전파 속성에 따라 기존의 트랜잭션에 참여하거나, 새로운 트랜잭션을 시작하거나, 에러를 발생시키는 등 다양한 행동을 선택할 수 있습니다. 이는 애플리케이션의 복잡한 비즈니스 로직을 처리하는데 유용하게 사용됩니다.

 

스프링 프레임워크의 org.springframework.transaction.annotation 패키지에 있는 @Transactional 어노테이션은 트랜잭션의 전파 레벨을 정할 수 있습니다.

// ex
@Transactional(propagation = Propagation.REQUIRED)

 

물리 트랜잭션과 논리 트랜잭션

물리 트랜잭션과 논리 트랜잭션 이미지

트랜잭션은 데이터베이스에서 제공하는 기술이므로 커넥션 객체를 통해 처리합니다 개의 트랜잭션을 사용한다는 것은 하나의 커넥션 객체를 사용한다는 것이고실제 데이터베이스의 트랜잭션을 사용한다는 점에서 물리 트랜잭션이라고도 합니다물리 트랜잭션은 실제 커넥션에 롤백커밋 호출하여 해당 트랜잭션의 종료를 의미합니다.

 

동작 과정

 

  1. 물리 트랜잭션 시작 (START TRANSACTION)
    • 물리 트랜잭션은 실제 데이터베이스에서 트랜잭션을 시작하는 단계입니다. 데이터베이스와 연결된 커넥션을 통해 트랜잭션이 시작됩니다.
    • 로직 수행 → 물리 트랜잭션 생성 → DB (START TRANSACTION)
  2. 논리 트랜잭션 시작, 물리 트랜잭션 참여
    • @Transactional이 적용된 로직이 호출되면 논리 트랜잭션이 시작됩니다.
    • 논리 트랜잭션 → 물리 트랜잭션 참여 (이미 존재하는 물리 트랜잭션에 참여 혹은 새로운 물리 트랜잭션 생성 요청)
  3. 논리 트랜잭션 종료
    • 논리 트랜잭션이 종료될 때, 그 안에 포함된 모든 물리 트랜잭션도 커밋하거나 롤백을 통해 종료되어야 합니다. 논리 트랜잭션이 성공적으로 커밋되면, 그에 속한 물리 트랜잭션도 커밋됩니다.
  4. 물리 트랜잭션 커밋
    • 모든 논리 트랜잭션이 종료되면, 물리 트랜잭션은 DB에 실제 변경을 반영하기 위해 커밋됩니다. 이때, 물리 트랜잭션의 상태가 DB에 반영되고 트랜잭션이 종료됩니다. (하나의 논리 트랜잭션이라도 실패 시 롤백)
    • 물리 트랜잭션 → DB (COMMIT)
  5. 하나의 트랜잭션 종료
    • 논리 트랜잭션과 그에 속한 모든 물리 트랜잭션이 커밋되면 전체 트랜잭션이 종료됩니다.

 

논리 트랜잭션은 스프링이 관리하지만, 물리 트랜잭션은 실제 데이터베이스와 연결되어 커밋, 롤백을 처리합니다.

  • 물리 트랜잭션: 실제 데이터베이스에 적용되는 트랜잭션으로, 커넥션을 통해 커밋, 롤백하는 단위
  • 논리 트랜잭션: 스프링이 트랜잭션 매니저를 통해 트랜잭션을 처리하는 단위
    • 모든 논리 트랜잭션이 커밋 되어야 트랜잭션이 커밋됩니다.
    • 하나의 논리 트랜잭션이라도 롤백되면 물리 트랜잭션 또한 롤백됩니다.

 

트랜잭션 전파 속성 옵션

// ex
@Transactional(propagation = Propagation.REQUIRED)

트랜잭션 전파 설정은 @Transactional의 옵션 propagation을 통해 설정이 가능합니다.

  • REQUIRED
  • REQUIRES_NEW
  • NESTED
  • MANDATORY
  • SUPPORTS
  • NOT_SUPPORTED
  • NEVER

 

트랜잭션의 전파 레벨에는 위와 같이 7가지가 존재합니다. 각 전파 레벨에 대해 알아보도록 하겠습니다.

 

REQUIRED : default

REQUIRED : default 이미지

REQUIRED는 스프링이 제공하는 기본(default) 전파 속성으로, 기본적으로 2개의 논리 트랜잭션을 묶어 1개의 물리 트랜잭션을 사용하는 것입니다.

 

REQUIRED 동작 과정

 

  1. 로직 수행 시작
    • 애플리케이션에서 로직1을 호출합니다.
  2. 물리 트랜잭션 생성
    • @Transactional(propagation = REQUIRED)가 적용된 로직2를 호출합니다.
  3. 로직1의 논리 트랜잭션 참여
    • 로직1은 생성된 물리 트랜잭션에 참여합니다.
  4. 로직1 → 로직2 호출
    • 로직1 내부에서 @Transactional(propagation = REQUIRED)가 적용된 로직2를 호출합니다.
  5. 로직2의 논리 트랜잭션 참여
    • 로직2는 새로운 논리 트랜잭션으로 간주되지만, 로직1과 동일한 물리 트랜잭션에 참여합니다.
  6. 로직2 논리 트랜잭션 종료
    • 로직2의 작업이 완료되면 논리 트랜잭션이 종료되며, 물리 트랜잭션은 그대로 유지됩니다.
  7. 로직1 논리 트랜잭션 종료
    • 로직1의 작업까지 완료되면 모든 논리 트랜잭션이 종료됩니다.
  8. 물리 트랜잭션 커밋
    • 모든 논리 트랜잭션이 정상적으로 커밋예약상태일 시 커밋됩니다. 여기서 단 하나의 논리 트랜잭션이라도 실패 시 롤백됩니다.
    • 물리 트랜잭션 → DB (COMMIT)

 

REQUIRES_NEW

REQUIRES_NEW 이미지

REQUIRES_NEW 외부 트랜잭션과 내부 트랜잭션을 완전히 분리하는 전파 속성입니다. 2개의 물리 트랜잭션이 사용되며, 각각 트랜잭션 별로 커밋과 롤백이 수행됩니다.

 

REQUIRES_NEW 동작 과정

 

  1. 로직 수행 시작
    • 애플리케이션에서 로직1을 호출합니다.
  2. 로직1의 물리 트랜잭션 시작, 논리 트랜잭션 참여
    • 로직1의 물리 트랜잭션이 시작되고, 로직1의 논리 트랜잭션이 해당 트랜잭션에 참여합니다.
  3. 로직1 → 로직2 호출
    • 로직2 호출 시 REQUIRES_NEW 전파 속성으로 인해 새로운 물리 트랜잭션이 생성되고 로직2의 논리 트랜잭션은 새로운 물리 트랜잭션에 참여합니다.
  4. 로직2 문제 발생
    • 로직2의 논리 트랜잭션이 종료되고 그에 해당하는 물리 트랜잭션이 롤백됩니다. 이때, 로직2의 물리 트랜잭션은 독립적으로 처리되어 로직1의 트랜잭션에 영향을 주지 않습니다.
  5. 로직1 종료
    • 로직1의 논리 트랜잭션이 종료되고 그에 해당하는 물리 트랜잭션이 커밋됩니다.

 

이 속성은 로직1로직2의 트랜잭션이 독립적으로 처리되도록 보장합니다.

즉, 로직2가 실패하더라도 로직1의 트랜잭션은 영향을 받지않아 로직2의 실패로 인한 로직1의 롤백은 진행되지 않습니다.

 

NESTED

NESTED 이미지

NESTED 동작 과정

 

  1. 부모 트랜잭션 존재 시 중첩 트랜잭션 생성
    • 부모 트랜잭션 존재 시, 새로운 중첩 트랜잭션을 생성합니다. 이 트랜잭션은 부모 트랜잭션의 일부로 취급되고 부모의 커밋, 롤백에 의존합니다.
    • 만약 부모 트랜잭션이 없다면, 새로운 독립적인 트랜잭션을 시작합니다. 이때 부모 트랜잭션과 별개의 트랜잭션이 처리됩니다.
  2. 중첩 트랜잭션 종료 시 커밋
    • 중첩 트랜잭션 완료 시, 커밋은 부모 트랜잭션의 끝에서 이루어집니다. 즉, 자식 트랜잭션이 종료되더라도 부모 트랜잭션이 커밋될 때까지 실제로 데이터베이스에 반영되지 않습니다.
  3. 중첩 트랜잭션 롤백 발생
    • 중첩 트랜잭션 롤백 시, 부모 트랜잭션에는 영향을 주지 않습니다. 즉, 중첩 트랜잭션의 실패로 인해 부모 트랜잭션이 롤백되지는 않습니다. (중첩 트랜잭션 시작 전 시점으로 롤백, 부모 트랜잭션은 정상 작동)
  4. 부모 트랜잭션 롤백 발생
    • 부모 트랜잭션 롤백 시, 모든 트랜잭션이 롤백됩니다.

 

MANDATORY

MANDATORY 이미지

반드시 부모 트랜잭션 하에서만 수행이 가능합니다. 만약, 부모 트랜잭션이 존재하지 않으면 IllegalTransactionStateException 예외를 발생시킵니다.

 

SUPPORTS

  • 부모 트랜잭션 존재 시 트랜잭션에 참여합니다.
  • 부모 트랜잭션이 존재하지 않으면 트랜잭션 없이 수행됩니다.

 

NOT_SUPPORTED

  • 부모 트랜잭션 존재 시 트랜잭션을 보류시키고 트랜잭션 없이 수행됩니다.
  • 부모 트랜잭션이 존재하지 않으면 정상적으로 트랜잭션 없이 수행됩니다.

 

NEVER

  • 어떠한 경우에도 트랜잭션을 생성하지 않습니다.
  • 부모 트랜잭션 존재 시 IlegalTransactionalStateException 예외를 발생시킵니다.

 

마무리

트랜잭션 전파는 애플리케이션의 안정성과 일관성을 보장합니다. 특히, 분산 시스템이나 복잡한 비즈니스 로직을 처리할 때, 적절한 전파 속성을 선택하는 것이 중요합니다.

 

트랜잭션 전파 속성 설명
REQUIRED 부모 트랜잭션이 있으면 참여, 없으면 새로운 트랜잭션 생성
REQUIRES_NEW 부모 트랜잭션과 상관없이 항상 새로운 트랜잭션 생성
NESTED 부모 트랜잭션이 있으면 중첩 트랜잭션 생성, 없으면 새로운 트랜잭션 생성
MANDATORY 부모 트랜잭션이 있으면 참여, 없으면 예외 발생
SUPPORTS 부모 트랜잭션이 있으면 참여, 없으면 트랜잭션 없이 실행
NOT_SUPPORTED 부모 트랜잭션이 있어도 트랜잭션 없이 실행
NEVER 트랜잭션을 생성하지 않으며, 부모 트랜잭션이 있으면 예외 발생