[Spring] Spring Transaction 핵심 요약

2025. 4. 10. 23:51·Spring
728x90
반응형

1. 트랜잭션 적용 확인

  • @Transactional로 선언적 트랜잭션을 사용하면, 실제로 트랜잭션이 적용되는지 확인하기 어렵다.
  • 스프링은 AOP 프록시를 이용해 트랜잭션을 적용한다. 따라서 실제 객체가 아닌 프록시 객체가 주입된다.
  • 트랜잭션이 활성화되었는지 확인하려면 TransactionSynchronizationManager.isActualTransactionActive() 등을 사용할 수 있다.
  • 로그 설정(logging.level.org.springframework.transaction.interceptor=TRACE)을 통해 트랜잭션 시작/종료를 자세히 확인 가능하다.

2. 트랜잭션 적용 위치

  • @Transactional을 클래스와 메서드에 동시에 붙이면, 메서드 레벨이 우선순위가 더 높다(더 구체적인 곳이 우선).
  • 클래스 레벨에 @Transactional이 있으면, 메서드에 따로 애노테이션이 없어도 기본적으로 트랜잭션이 적용된다.
  • 과거에는 인터페이스 레벨에 @Transactional을 붙이는 방식을 쓰기도 했지만, 스프링 공식 문서에서는 구현 클래스(구체 클래스)에 사용하는 것을 권장한다.

3. 트랜잭션 AOP 주의 사항 - 프록시 내부 호출

  • @Transactional은 기본적으로 프록시 객체를 통해 트랜잭션을 적용한다.
  • 자기 자신 내부에서 메서드를 직접 호출하면(내부 호출), 프록시를 거치지 않으므로 트랜잭션이 적용되지 않는다(“프록시 우회 문제”).
  • 이를 해결하려면, 트랜잭션이 필요한 메서드를 별도의 클래스로 분리해서 호출하거나, 인터페이스/설계 구성을 조정해야 한다.

4. 트랜잭션 AOP 주의 사항 - 초기화 시점

  • @PostConstruct 초기화 메서드는 스프링 컨테이너가 AOP를 적용하기 전에 실행되므로, 여기에 @Transactional을 붙여도 트랜잭션이 적용되지 않는다.
  • 트랜잭션이 확실히 필요한 초기화 로직은 ApplicationReadyEvent를 이용한 이벤트 리스너(@EventListener) 등을 활용하면 AOP 적용 이후에 호출되어 정상 작동한다.
@EventListener(value = ApplicationReadyEvent.class)
@Transactional
public void init() {
    log.info("Hello init ApplicationReadyEvent");
}

5. 트랜잭션 옵션 소개

@Transactional에서 제공하는 주요 속성:

  1. rollbackFor, noRollbackFor
    • 기본 정책: 언체크 예외(RuntimeException, Error) → 롤백, 체크 예외(Exception) → 커밋
    • 필요에 따라 rollbackFor = Exception.class 등으로 체크 예외도 롤백하게 설정 가능
  2. propagation
    • 트랜잭션 전파 방식을 지정. REQUIRED, REQUIRES_NEW 등이 있다(뒤에서 자세히 설명).
  3. isolation
    • DB 격리 수준(READ\_UNCOMMITTED, READ\_COMMITTED, etc.). 일반적으로 DB 기본 설정을 그대로 사용한다.
  4. timeout
    • 트랜잭션 수행 제한 시간을 초 단위로 지정.
  5. readOnly
    • 읽기 전용 트랜잭션을 사용할 때 설정하면 성능 최적화 등에 도움이 된다(JPA의 플러시 최소화 등).

6. 예외와 트랜잭션 커밋, 롤백

  • 기본 정책
    • 언체크 예외(RuntimeException, Error) 발생 시 → 자동 롤백
    • 체크 예외(Exception) 발생 시 → 자동 커밋
  • 비즈니스 상황에서의 “체크 예외”를 롤백 처리하고 싶다면 @Transactional(rollbackFor=Exception.class) 처럼 추가 설정이 필요하다.
  • 커밋/롤백 결과와 달리, 개발자가 기대하는 결과가 다르면 UnexpectedRollbackException 같은 예외가 발생하므로 주의해야 한다.
@Slf4j
@Service
@RequiredArgsConstructor
public class OrderService {
    private final OrderRepository orderRepository;
    //JPA는 트랜잭션 커밋 시점에 Order 데이터를 DB에 반영한다.

    @Transactional
    public void order(Order order) throws NotEnoughMoneyException {
        log.info("order 호출");

        orderRepository.save(order);

        log.info("결제 프로세스 진입");

        if (order.getUsername().equals("예외")) {
            log.info("시스템 예외 발생");
            throw new RuntimeException("시스템 예외");
        } else if (order.getUsername().equals("잔고부족")) {
            log.info("잔고 부족 비즈니스 예외 발생");
            order.setPayStatus("대기");
            throw new NotEnoughMoneyException("잔고가 부족합니다");
        } else {
            //정상 승인
            log.info("정상 승인");
            order.setPayStatus("완료");
        }

        log.info("결제 프로세스 완료");
    }
}

7. 스프링 트랜잭션 전파 - 기본 개념

  • 한 메서드에서 트랜잭션을 시작한 뒤(“외부 트랜잭션”)에, 내부에서 또 다른 트랜잭션(“내부 트랜잭션”)이 호출되면, 기본 전파 모드는 REQUIRED라서 하나의 물리 트랜잭션으로 묶인다.
  • 내부 트랜잭션이 커밋해도 실제 DB 커밋은 일어나지 않는다(논리적 커밋). 실제 커밋은 최초 트랜잭션(외부)이 끝날 때 일어난다.
  • 내부에서 롤백이 발생하면 전체 트랜잭션이 롤백된다. 외부에서 커밋을 시도해도 이미 롤백 전용(rollback-only) 마크가 찍혀있으면 UnexpectedRollbackException이 발생한다.
  • 외부와 내부 트랜잭션이 AND 연산으로 묶인 느낌이다. 결국 모두 성공해야 커밋되고, 하나라도 실패하면 롤백된다.
  • 같은 물리 트랜잭션 내에 있는 트랜잭션들끼리는 TransactionSynchronizationManager를 통해 같은 DB 커넥션을 공유한다.
    • REQUIRED는 이미 트랜잭션이 진행 중이면 그 트랜잭션에 참여 => 트랜잭션을 새로 시작하지 않고, 현재 쓰레드의 DB 커넥션을 그대로 꺼내서 사용

8. 트랜잭션 전파 - REQUIRES_NEW

  • REQUIRES\_NEW를 사용하면 기존 트랜잭션을 잠시 “중단(suspend)”하고, 완전히 새로운 트랜잭션(새 DB 커넥션)으로 진행한다.
  • 내부 트랜잭션이 롤백되어도 외부 트랜잭션에는 영향을 주지 않고, 반대로 외부가 롤백해도 내부에는 영향을 주지 않는다. 즉, 각 트랜잭션이 독립적으로 커밋/롤백된다.
  • 주의할 점은 DB 커넥션을 2개 이상 동시에 사용하게 되므로, 리소스 사용량에 대한 고려가 필요하다.

9. 그 외 다양한 전파 옵션

  1. REQUIRED(기본값)
    • 트랜잭션이 있으면 참여, 없으면 새로 시작
  2. REQUIRES_NEW
    • 항상 새 트랜잭션 시작(기존 트랜잭션은 중단)
  3. SUPPORTS
    • 트랜잭션이 있으면 참여, 없으면 트랜잭션 없이 진행
  4. NOT_SUPPORTED
    • 기존 트랜잭션이 있어도 중단하고, 트랜잭션 없이 진행
  5. MANDATORY
    • 반드시 기존 트랜잭션이 있어야 참여. 없으면 예외 발생
  6. NEVER
    • 트랜잭션이 있으면 예외. 트랜잭션 없이만 진행
  7. NESTED
    • 중첩 트랜잭션. 외부 트랜잭션의 영향을 받지만, 내부 트랜잭션의 롤백이 외부 트랜잭션에는 직접 영향을 주지 않는 구조(savepoint 이용). 단, JPA에서는 공식 지원이 어려운 편.
728x90
반응형

'Spring' 카테고리의 다른 글

[Spring] 스프링의 데이터 접근 예외 추상화와 JdbcTemplate  (0) 2025.04.07
[Spring Core] Spring Core 핵심 개념  (0) 2025.02.15
[Spring Core] 스프링을 사용하는 이유?  (0) 2025.02.15
'Spring' 카테고리의 다른 글
  • [Spring] 스프링의 데이터 접근 예외 추상화와 JdbcTemplate
  • [Spring Core] Spring Core 핵심 개념
  • [Spring Core] 스프링을 사용하는 이유?
mxruhxn
mxruhxn
소소하게 개발 공부 기록하기
    반응형
    250x250
  • mxruhxn
    maruhxn
    mxruhxn
  • 전체
    오늘
    어제
    • 분류 전체보기 (150)
      • Java (21)
      • Spring (4)
      • Database (13)
      • Operating Syste.. (1)
      • Computer Archit.. (0)
      • Network (24)
      • Data Structure (6)
      • Algorithm (11)
      • Data Infra (7)
      • DevOps (12)
      • ETC (27)
      • Project (21)
      • Book (1)
      • Look Back (1)
  • 블로그 메뉴

    • 링크

      • Github
    • 공지사항

    • 인기 글

    • 태그

    • 최근 댓글

    • 최근 글

    • hELLO· Designed By정상우.v4.10.0
    mxruhxn
    [Spring] Spring Transaction 핵심 요약
    상단으로

    티스토리툴바