Spring 트랜잭션 핵심 정리

2025. 8. 8. 10:57·Spring

정리 배경

@Transactional은 처음에는 단순해 보이지만, 실제로 사용해보면 예상과 다른 동작을 겪게 됩니다. 전파 옵션(REQUIRED, REQUIRES_NEW), rollback-only, 프록시, ThreadLocal 같은 개념들이 얽혀 있기 때문입니다.

이번 정리의 목표는 다음 세 가지입니다.

  1. 트랜잭션 전파 방식 이해 (REQUIRED vs REQUIRES_NEW)
  2. rollback-only가 언제 설정되고, 어떻게 동작하는지
  3. 트랜잭션 매니저가 싱글톤인데 상태 충돌이 발생하지 않는 이유

1. 트랜잭션 전파 (Propagation)

REQUIRED (기본값)

@Transactional
public void outer() {
    inner();
}
  • outer()에서 트랜잭션이 시작되면, inner()는 동일한 트랜잭션을 공유합니다.
  • inner()에서 예외가 발생하면 전체 트랜잭션이 롤백됩니다.
  • 동일 트랜잭션을 공유하므로, rollback-only가 설정되면 상위 메서드도 롤백됩니다.

REQUIRES_NEW

@Transactional
public void outer() {
    try {
        inner(); // 별도 트랜잭션
    } catch (Exception e) {
        // outer 트랜잭션은 커밋 가능
    }
}

@Transactional(propagation = Propagation.REQUIRES_NEW)
public void inner() {
    throw new RuntimeException();
}
  • inner() 실행 시 기존 트랜잭션은 잠시 중단되고, 새로운 트랜잭션이 시작됩니다.
  • inner()에서 예외가 발생해도 outer() 트랜잭션에는 영향을 주지 않습니다.
  • 하위 트랜잭션이 실패하더라도 상위 트랜잭션은 커밋 가능합니다.

2. rollback-only 플래그

예외 발생 시 rollback-only 마킹

@Transactional
public void method() {
    throw new RuntimeException();
}
  • RuntimeException이 발생하면 현재 트랜잭션은 rollback-only 상태로 전환됩니다.
  • 이후 커밋을 시도해도 무조건 롤백됩니다.

rollback-only 상태에서 예외를 catch해도 무의미

@Transactional
public void facade() {
    try {
        child();
    } catch (Exception e) {
        // 예외는 잡았지만 rollback-only는 이미 설정됨
    }
}

@Transactional
public void child() {
    throw new RuntimeException();
}
  • REQUIRED 전파이므로 facade()와 child()는 같은 트랜잭션을 사용합니다.
  • child()에서 예외가 발생하면 rollback-only가 설정됩니다.
  • 예외를 facade()에서 잡더라도, 커밋 시점에 롤백되며 UnexpectedRollbackException이 발생할 수 있습니다.

3. 싱글톤 트랜잭션 매니저와 ThreadLocal

트랜잭션 매니저는 싱글톤, 상태는 ThreadLocal로 관리

  • PlatformTransactionManager는 스프링 빈으로 싱글톤으로 관리됩니다.
  • 트랜잭션의 상태(rollback-only, 커넥션 등)는 ThreadLocal을 사용해 스레드마다 분리 저장됩니다.
  • 덕분에 여러 요청이 동시에 들어와도 트랜잭션 간 상태 충돌이 발생하지 않습니다.

관련 클래스 요약

클래스역할
TransactionStatus 트랜잭션 상태 보관 (e.g. isRollbackOnly)
TransactionSynchronizationManager 현재 스레드의 트랜잭션 리소스를 저장
AbstractPlatformTransactionManager 트랜잭션 시작/커밋/롤백 로직 처리

핵심 요약

  • REQUIRED는 트랜잭션을 공유하므로, 하위 메서드에서 예외가 발생하면 rollback-only가 설정되어 전체 트랜잭션이 롤백됩니다.
  • 예외를 catch하더라도 rollback-only 상태에서는 트랜잭션 커밋이 불가능합니다.
  • REQUIRES_NEW는 별도의 트랜잭션을 생성하므로, 하위 트랜잭션 실패가 상위 트랜잭션에 영향을 주지 않습니다.
  • 트랜잭션 매니저는 싱글톤이지만 ThreadLocal 덕분에 스레드별로 상태가 안전하게 분리됩니다.

정리하면서 느낀 점

  • rollback-only는 한 번 설정되면 되돌릴 수 없습니다. 예외를 잡았는지보다 트랜잭션이 커밋 가능한 상태인지가 더 중요합니다.
  • ThreadLocal은 단순히 동시성 문제 해결을 넘어서, 트랜잭션 격리에 핵심적인 역할을 합니다.
  • REQUIRES_NEW는 실패할 수 있는 작업(예: 감사 로그)을 분리 처리할 때 유용합니다.

'Spring' 카테고리의 다른 글

ApplicationEvent로 비즈니스 경계 나누기: 고민과 선택  (3) 2025.08.28
결제 시스템에 Circuit Breaker를 도입하며 – 계층별 책임 분리와 안정성 확보 전략  (1) 2025.08.22
DB 인덱스 설계와 Redis 캐싱(Port-Adapter) 적용기  (2) 2025.08.15
도메인 검증 책임은 어디에 둘까요? – Service vs Facade, 저는 이렇게 선택했습니다  (2) 2025.08.01
@Transactional 내부 호출 문제와 해결 방법  (0) 2025.03.12
'Spring' 카테고리의 다른 글
  • 결제 시스템에 Circuit Breaker를 도입하며 – 계층별 책임 분리와 안정성 확보 전략
  • DB 인덱스 설계와 Redis 캐싱(Port-Adapter) 적용기
  • 도메인 검증 책임은 어디에 둘까요? – Service vs Facade, 저는 이렇게 선택했습니다
  • @Transactional 내부 호출 문제와 해결 방법
JoshDev
JoshDev
    • 분류 전체보기 (24)
      • Java (3)
      • Spring (9)
      • Test Code (2)
      • WIL (6)
      • Vue.js (2)
      • WEB (0)
      • DB (1)
        • MySQL (1)
  • 인기 글

  • hELLO· Designed By정상우.v4.10.4
JoshDev
Spring 트랜잭션 핵심 정리
상단으로

티스토리툴바