기술과 산업/언어 및 프레임워크

Spring Boot 시리즈 17편 – 트랜잭션 관리 고급 전략: 선언적, 프로그래밍 방식, 전파/고립 수준까지 완벽 정리

B컷개발자 2025. 4. 28. 16:49
728x90

Spring Boot에서 트랜잭션 관리를 고급 수준으로 다루는 방법을 정리했습니다. 선언적 트랜잭션, 프로그래밍 방식, 전파(Propagation), 고립(Isolation) 수준까지 실전 중심으로 설명합니다.


Spring Boot 시리즈 17편 – 트랜잭션 관리 고급 전략: 선언적, 프로그래밍 방식, 전파/고립 수준까지 완벽 정리

트랜잭션(Transaction)은 데이터 무결성과 일관성을 유지하는 핵심 메커니즘입니다.
Spring Boot에서는 @Transactional 하나로 간단히 사용할 수 있지만,
상황에 맞는 전파(Propagation) 설정, 고립 수준(Isolation) 선택, 복잡한 트랜잭션 흐름 제어를 제대로 이해하지 못하면
의도치 않은 데이터 오류나 성능 저하를 초래할 수 있습니다.

이번 글에서는 실제 서비스 수준에서 필요한 트랜잭션 고급 관리 전략을 체계적으로 정리합니다.


📌 1. 트랜잭션 관리 방식 두 가지

방식 설명 특징

선언적(Declarative) @Transactional 애노테이션 사용 코드 깔끔, 대부분 케이스 대응 가능
프로그래밍 방식(Programmatic) TransactionTemplate, PlatformTransactionManager 직접 사용 세밀한 흐름 제어 가능, 복잡도↑

✅ 2. 선언적 트랜잭션 (@Transactional)

기본 사용법

@Service
@Transactional
public class OrderService {
    
    public void placeOrder(OrderRequest request) {
        // 주문 생성, 재고 감소, 포인트 적립 등
    }
}
  • 클래스 레벨에 선언하면 모든 public 메서드에 트랜잭션 적용
  • 메서드 레벨에 별도로 선언할 수도 있음

주요 속성

속성 기본값 설명

propagation REQUIRED 기존 트랜잭션 있으면 참여, 없으면 새로 생성
isolation DEFAULT DB 기본 설정 사용
rollbackFor Exception.class 어떤 예외가 발생했을 때 롤백할지 지정
timeout -1 (무제한) 트랜잭션 제한 시간 설정
readOnly false 읽기 전용 힌트 (최적화에 도움)

🔄 3. 트랜잭션 전파(Propagation) 상세 정리

트랜잭션 전파는 "트랜잭션이 이미 있을 때 어떻게 동작할 것인가"를 제어합니다.

옵션 설명 사용 예시

REQUIRED (기본) 있으면 참여, 없으면 새로 시작 대부분의 서비스 로직
REQUIRES_NEW 항상 새 트랜잭션 시작 (기존은 일시 중단) 이벤트 기록, 별도 보장 로직
NESTED 기존 트랜잭션 내부에 Savepoint 생성 부분 롤백이 필요한 경우
SUPPORTS 있으면 참여, 없으면 비트랜잭션 실행 선택적 트랜잭션
NOT_SUPPORTED 트랜잭션 없이 실행 로그 기록 등 성능 민감 작업
NEVER 트랜잭션 있으면 예외 발생 트랜잭션 금지 영역

예시: REQUIRES_NEW 사용

@Transactional(propagation = Propagation.REQUIRES_NEW)
public void logEvent(Event event) {
    eventRepository.save(event);
}
  • 메인 비즈니스 트랜잭션 실패 여부와 관계없이 이벤트 기록은 반드시 남겨야 할 때 사용

⚙️ 4. 트랜잭션 고립 수준(Isolation) 설정

트랜잭션 고립 수준은 **동시성 문제(Dirty Read, Phantom Read, Non-repeatable Read 등)**를 어떻게 다룰지를 결정합니다.

수준 설명 동시성 제어 수준

READ_UNCOMMITTED 커밋 안 된 데이터 읽기 허용 매우 낮음 (Dirty Read 발생 가능)
READ_COMMITTED 커밋된 데이터만 읽기 기본값 (SQL Server, Oracle)
REPEATABLE_READ 조회한 데이터는 트랜잭션 동안 일관 MySQL InnoDB 기본
SERIALIZABLE 모든 트랜잭션 직렬화 처리 최고 수준, 성능 저하 큼

예시: SERIALIZABLE 적용

@Transactional(isolation = Isolation.SERIALIZABLE)
public void criticalStockCheck() {
    // 재고 수량을 엄격하게 일관성 있게 조회
}
  • 동시 재고 감소, 결제 처리 등 강한 일관성이 필요한 작업에 사용

🔥 5. 프로그래밍 방식 트랜잭션 관리

더 세밀한 흐름 제어가 필요할 경우 TransactionTemplate을 사용할 수 있습니다.

@Service
public class ManualTransactionService {

    private final TransactionTemplate transactionTemplate;

    public ManualTransactionService(PlatformTransactionManager transactionManager) {
        this.transactionTemplate = new TransactionTemplate(transactionManager);
    }

    public void executeManualTransaction() {
        transactionTemplate.executeWithoutResult(status -> {
            // 여기서 로직 실행
        });
    }
}
  • 복잡한 분기/조건부 커밋/조건부 롤백이 필요한 경우 활용
  • 선언적 방식보다 복잡도는 높지만 제어력은 더 강력

🧠 실무 트랜잭션 설계 팁

항목 전략

서비스 레이어 중심 트랜잭션은 반드시 서비스(Service) 계층에만 선언
내부 호출 주의 같은 클래스 내 메서드 호출은 프록시 적용되지 않음 (self-injection 필요)
분리된 책임 관리 트랜잭션 경계를 로직 단위가 아닌 비즈니스 단위로 설정
커스텀 롤백 rollbackFor = CustomException.class로 특정 예외 롤백 지정
DB 락 주의 SERIALIZABLE 사용 시 DB 락 장기 점유 주의 필요

✅ 마무리 요약

항목 요약

기본 사용 @Transactional로 간단하게 선언
고급 설정 Propagation, Isolation, Rollback 조건 세밀 제어
프로그래밍 방식 TransactionTemplate으로 복잡 흐름 대응 가능
실무 설계 서비스 레이어 중심, 동시성/일관성 균형 맞추기
테스트 전략 DB 롤백 테스트 (@Transactional + @Rollback) 적극 활용

📌 다음 편 예고

Spring Boot 시리즈 18편: 멀티 모듈 프로젝트의 테스트 전략 – 독립성, 속도, 신뢰성 확보 방법

 

728x90