Java 아키텍처·Spring 용어 사전
트랜잭션propagation · REQUIRES_NEW · REQUIRED

트랜잭션 전파

이미 트랜잭션이 있을 때 어떻게 할지 정하는 옵션. REQUIRED가 기본이고 REQUIRES_NEW는 커넥션을 하나 더 쓴다.

이미 진행 중인 트랜잭션이 있을 때 새 트랜잭션 메서드를 어떻게 처리할지 정하는 옵션.

주요 값

기존 트랜잭션이 있으면없으면
REQUIRED(기본)참여한다새로 시작
REQUIRES_NEW일시 중단하고 새로 시작새로 시작
SUPPORTS참여트랜잭션 없이 실행
MANDATORY참여예외
NOT_SUPPORTED중단하고 트랜잭션 없이트랜잭션 없이
NEVER예외트랜잭션 없이
NESTED세이브포인트를 만든다새로 시작

REQUIRED — 하나로 합쳐진다

@Transactional void outer() { inner(); }        // 같은 트랜잭션
@Transactional void inner() { ... }

물리 트랜잭션은 하나다

  • inner 가 실패하면 outer 까지 함께 롤백된다
  • "로그만 남기고 계속 진행" 이 불가능하다

REQUIRES_NEW — 독립시킨다

@Transactional void outer() {
    repo.save(order);
    try { logService.write(...); } catch (Exception ignored) { }   // 실패해도 주문은 유지
}

@Transactional(propagation = Propagation.REQUIRES_NEW)
void write(...) { ... }

감사 로그처럼 "본 작업과 운명을 같이하면 안 되는" 작업에 쓴다.

REQUIRES_NEW의 비용 — 커넥션을 하나 더 쓴다

바깥 트랜잭션이 커넥션 1개를 쥔 채 대기하고 안쪽이 커넥션을 하나 더 빌린다

  • 요청당 커넥션 2개

풀 크기가 10인데 동시 요청 10개가 들어오면

  • 전부 첫 커넥션을 쥐고 두 번째를 기다린다 → 데드락

커넥션 풀 크기가 이 패턴의 상한이라는 것을 반드시 계산해야 한다.

자기 호출에서는 전파가 동작하지 않는다

@Transactional
void outer() { this.inner(); }                     // 프록시를 안 탄다
@Transactional(propagation = REQUIRES_NEW)
void inner() { ... }                               // 그냥 outer 트랜잭션에서 실행된다

전파 옵션 문제로 보이는 사고의 상당수가 사실 자기 호출이다.

면접 함정

  • "REQUIRES_NEW로 하면 안전하다" → 커넥션을 두 배로 쓴다. 풀 고갈·데드락의 원인이 된다.
  • "NESTED는 어디서나 된다" → JDBC 세이브포인트를 지원하는 환경이어야 하고, JPA에서는 제약이 있다.

지금 트랜잭션 상태를 확인한다

TransactionSynchronizationManager.isActualTransactionActive();   // 트랜잭션 안인가
TransactionSynchronizationManager.getCurrentTransactionName();   // 어느 메서드가 열었나
TransactionSynchronizationManager.isCurrentTransactionReadOnly();
# 로그로 경계를 추적한다
logging.level.org.springframework.transaction.interceptor=TRACE
# Creating new transaction with name [...]
# Participating in existing transaction        ← REQUIRED 가 합류한 것
# Suspending current transaction, creating new ← REQUIRES_NEW

"Participating in existing"이 찍히면 새 트랜잭션이 아니라 기존에 합류한 것이다. 전파 옵션이 의도대로 동작하는지 확인하는 가장 확실한 방법이다.

UnexpectedRollbackException이 뜨는 이유

inner(REQUIRED) 가 예외로 실패한다

  • 물리 트랜잭션에 rollback-only 표시가 붙는다 outer 가 그 예외를 try-catch 로 삼킨다

  • outer 는 커밋을 시도한다

  • 이미 rollback-only 라 커밋할 수 없다

  • UnexpectedRollbackException: Transaction silently rolled back

"예외를 잡았는데 왜 롤백되지" 의 답이다. 삼키고 계속 진행하려면 inner를 REQUIRES_NEW로 독립시켜야 한다.

함께 보면 좋은 용어

노트에서 맥락과 함께 보기 — 트랜잭션 — 전파·격리·롤백 함정