2025-05-29

스프링 트랜잭션

SpringTransactionJPA트랜잭션격리 수준

스프링 트랜잭션

Spring에서 트랜잭션을 올바르게 이해하고 활용하는 방법을 다룹니다. 프록시 기반 동작 원리, 전파 속성, 격리 수준, 예외 처리 시나리오를 살펴봅니다.


1. @Transactional의 동작 원리 (Proxy 기반)

Spring은 @Transactional을 만나면 실제 빈 대신 프록시 객체를 생성하여 트랜잭션을 관리합니다.

호출 흐름:

  • Client → Proxy Bean 호출
  • Proxy가 TransactionManager.getTransaction()으로 트랜잭션 시작
  • Target(비즈니스 로직) 실행
  • 예외 발생 여부에 따라 Commit 또는 Rollback
  • 트랜잭션 종료
@Service
public class OrderService {
    @Transactional
    public void placeOrder(OrderDto dto) {
        // 1) 재고 확인
        // 2) 결제 처리
        // 3) 주문 저장 (INSERT)
    }
}

2. 트랜잭션 전파 (Propagation)

메서드 간 트랜잭션 경계를 어떻게 공유할지 결정합니다.

| 전파 속성 | 설명 | |---|---| | REQUIRED | 기존 트랜잭션에 참여하거나 새로 생성 (기본값) | | REQUIRES_NEW | 항상 새 트랜잭션 생성; 기존 트랜잭션 일시 중단 | | SUPPORTS | 기존 트랜잭션이 있으면 참여; 없으면 트랜잭션 없이 실행 | | NOT_SUPPORTED | 기존 트랜잭션이 있으면 일시 중단; 트랜잭션 없이 실행 | | MANDATORY | 기존 트랜잭션 필수 | | NEVER | 기존 트랜잭션이 있으면 거부 | | NESTED | 기존 트랜잭션 내에 중첩 트랜잭션 생성 (Savepoint) |

@Transactional
public void outer() {
    inner();   // inner()가 REQUIRED이면 outer의 트랜잭션에 참여
}
 
@Transactional(propagation = REQUIRES_NEW)
public void inner() {
    // 독립 트랜잭션; inner의 롤백이 outer에 영향 없음
}

3. 격리 수준 (Isolation Level)

동시성 문제를 어떻게 관리할지 제어합니다.

| 수준 | 설명 | 문제 | |---|---|---| | READ_UNCOMMITTED | 커밋되지 않은 데이터 읽기 허용 | Dirty Read | | READ_COMMITTED | 커밋된 데이터만 읽기 (DB 기본값) | Non-Repeatable Read | | REPEATABLE_READ | 동일 쿼리가 동일한 결과 반환 | Phantom Read | | SERIALIZABLE | 완전한 격리 (직렬화) | — |

@Transactional(isolation = Isolation.REPEATABLE_READ)
public void updateInventory(Long productId, int qty) {
    // 같은 트랜잭션 내 쿼리는 일관된 재고 수량을 반환
}

핵심 개념:

  • Dirty Read: 다른 트랜잭션의 커밋되지 않은 데이터 읽기
  • Non-Repeatable Read: 같은 트랜잭션 내에서 재조회 시 값이 다름
  • Phantom Read: 같은 조건으로 재조회 시 새로운 행이 나타남

4. 롤백되지 않는 예외

기본적으로 @Transactional은 런타임 예외/에러에서 롤백하지만, Checked 예외에서는 커밋합니다.

@Transactional
public void process() {
    throw new IOException("Checked exception");   // 기본적으로 롤백되지 않음
}

롤백 조건 변경:

@Transactional(rollbackFor = Exception.class)
public void processAll() throws IOException {
    // IOException도 롤백 트리거
}

롤백 제외:

@Transactional(noRollbackFor = CustomException.class)
public void noRollback() {
    throw new CustomException();
    // 예외가 발생해도 롤백되지 않음
}

마무리

  • @Transactional은 프록시 기반으로 동작하며, 같은 클래스 내부 호출에서는 우회됨
  • 동시성 요구사항에 맞게 전파 속성과 격리 수준을 적절히 설정
  • 기본 RuntimeException 동작을 넘어서 rollbackFor/noRollbackFor로 조정