본문 바로가기
Java

@Transactional을 통한 스프링 트랜잭션의 전파

2024. 2. 20.
반응형

트랜잭션이 진행 중인 상황에서 추가로 트랜잭션을 수행하는 경우에 어떻게 동작해야 하는지 결정하는 것을 트랜잭션 전파라고 한다. 스프링에서의 트랜잭션 전파 과정에 대해 알아보자.

 

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

  • 트랜잭션을 각각 사용하는 것이 아니라, 트랜잭션이 이미 진행 중인데 여기에 추가로 트랜잭션을 수행한다면 어떻게 될까?
    • 이 경우에 어떻게 동작해야 하는지 결정하는 것을 트랜잭션 전파(propagation)라고 한다.
  • 스프링은 이해를 위해 물리 트랜잭션과 논리 트랜잭션이라는 개념을 나누었다.
    • 물리 트랜잭션: 실제 DB에 적용되는 트랜잭션. 실제 커넥션을 통해 트랜잭션을 시작하고 커밋/롤백하는 단위이다.
    • 논리 트랜잭션: 트랜잭션 매니저를 통해 트랜잭션을 사용하는 단위이다.
원칙
- 모든 논리 트랜잭션이 커밋되어야 물리 트랜잭션이 커밋된다.
- 하나의 논리 트랜잭션이라도 롤백되면 물리 트랜잭션은 롤백된다.

 

트랜잭션 전파 과정

1. 정상 동작하는 경우 - 외부 및 내부 모두 커밋

  • 외부 트랜잭션이 시작하면 트랜잭션 매니저는 트랜잭션 동기화 매니저를 확인하여 기존에 트랜잭션이 없음을 확인하고 물리 트랜잭션을 시작 및 동기화 매니저에 보관한다.
  • 내부 트랜잭션이 시작하면 트랜잭션 매니저는 트랜잭션 동기화 매니저를 확인하여 기존 트랜잭션이 있음을 확인하고 기존 트랜잭션에 참여한다. (아무것도 하지 않는다)
  • 트랜잭션 매니저는 커밋 시점에 트랜잭션이 신규인지 여부에 따라 다르게 동작한다. 내부 트랜잭션은 신규가 아니기 때문에 실제 커밋을 호출하지 않는다.
    • 내부 트랜잭션에서 실제 커밋을 호출한다면 트랜잭션이 끝나 커넥션이 종료되기 때문에 외부 트랜잭션이 커밋이나 롤백을 수행할 수 없게 된다.
  • 외부 트랜잭션 커밋 시점에는 신규 트랜잭션이기 때문에 실제 커밋을 호출하고 트랜잭션이 종료된다.

트랜잭션 매니저에 커밋을 호출해도 항상 실제 물리 커밋이 발생하지는 않는다.

 

2. 외부 트랜잭션이 롤백되는 경우

  • 외부 트랜잭션 코드에서 롤백을 요청하면 트랜잭션 매니저는 신규 트랜잭션임을 확인하고 실제 물리 롤백을 호출한다.

 

3. 내부 트랜잭션이 롤백되는 경우

  • 내부 트랜잭션이 코드에서 롤백을 요청하면 트랜잭션 매니저는 신규 트랜잭션인지 확인한다. 신규 트랜잭션이 아니기 때문에 실제 롤백을 호출하지 않는다. 다만 기존 트랜잭션에 롤백 전용 표시(rollbackOnly=true)를 한다.
  • 외부 트랜잭션 커밋 시점에 트랜잭션 매니저는 rollbackOnly 설정을 확인한다. 롤백 전용 표시가 되어있다면 실제 물리 롤백을 호출한다.
  • UnexpectedRollbackException
    • 커밋을 호출했지만 롤백 전용 표시로 인해 실제로는 롤백이 일어났으므로, 기대하지 않은 롤백이 일어났다는 런타임 예외를 던진다.

 

트랜잭션 전파 옵션

REQUIRED

  • 기본 설정값이다. 실무에서 대부분 사용하는 옵션이다.
  • 트랜잭션이 필수(required)라는 의미이다. 기존 트랜잭션이 없으면 생성하고, 있으면 참여한다.

REQUIRES_NEW

  • 항상 새로운 트랜잭션을 생성한다.
  • 트랜잭션 시작 시에 REQUIRES_NEW 옵션으로 설정되어 있다면 항상 새로운 트랜잭션을 생성한다. 만약 기존 트랜잭션이 있다면 해당 트랜잭션은 잠시 보류해두고, 현재 트랜잭션이 끝날 때까지 현재 트랜잭션만 사용한다. 트랜잭션이 끝나면 보류해두었던 기존 트랜잭션을 다시 사용한다.
  • 물리 트랜잭션을 분리하여 사용할 수 있지만 DB 커넥션이 한 요청(스레드)에서 동시에 2개가 사용되므로 주의해야 한다.
    • 사용량이 몰리는 서비스에서는 실사용자는 500명인데 커넥션은 1000개가 사용되어서 커넥션이 모자라 사용자를 더 못받는 문제가 생길 수 있다.
    • 간단한 구조 변경으로 문제를 해결할 수 있는 경우는 그렇게 하고, 구조가 너무 복잡해지는 경우에만 위 문제점을 주의해서 REQUIRES_NEW 옵션을 사용하도록 하자.

기타

  • SUPPORT: 트랜잭션이 있으면 참여, 없으면 없이 진행
  • NOT_SUPPORT: 항상 트랜잭션 없이 진행 (기존꺼는 보류)
  • MANDATORY: 트랜잭션이 있으면 참여, 없으면 IllegalTransactionStateException 예외 발생
  • NEVER: 트랜잭션이 없으면 없이 진행, 있으면 IllegalTransactionStateException 예외 발생
  • NESTED: 트랜잭션이 있으면 참여, 없으면 중첩 트랜잭션 생성(외부 트랜잭션에 영향을 받지만 영향을 주지 않음). JPA에서는 사용 불가
728x90
반응형

댓글