본문으로 건너뛰기
개발 머꼬
개발 노트Spring
hohyeon.dev22

@Transactional을 붙였는데 롤백이 안 되는 이유

  • #Engineering Note
  • #Spring
  • #Transaction

문제 발생

비즈니스 예외를 던졌는데 DB에 반영된 데이터가 롤백되지 않고 그대로 커밋된 경우가 있었습니다.

@Transactional
public void placeOrder(Order order) throws InsufficientStockException {
    orderRepository.save(order);
    if (stock < order.getQuantity()) {
        throw new InsufficientStockException(); // 체크 예외
    }
}

원인 분석

Spring의 @Transactional기본적으로 RuntimeException(및 Error)이 발생했을 때만 롤백합니다. InsufficientStockException처럼 Exception을 직접 상속한 체크 예외(checked exception)는 기본 설정으로는 롤백을 유발하지 않고 트랜잭션이 그대로 커밋됩니다. 이건 실수가 아니라 Spring의 의도된 기본 동작입니다 — EJB의 관례를 따른 것으로, 체크 예외는 "비즈니스적으로 예상 가능한 상황"으로 간주해 커밋하는 것을 기본값으로 삼았습니다.

해결 방안

  1. 체크 예외에서도 롤백이 필요하면 rollbackFor를 명시합니다.
@Transactional(rollbackFor = InsufficientStockException.class)
public void placeOrder(Order order) throws InsufficientStockException {
    // ...
}
  1. 가능하면 비즈니스 예외를 RuntimeException 기반으로 만들어 기본 롤백 규칙에 맞춥니다 — 대부분의 Spring 프로젝트가 이 방식을 씁니다.
  2. **Propagation(전파 속성)**도 자주 헷갈리는 부분입니다. 기본값 REQUIRED는 이미 트랜잭션이 있으면 참여하고 없으면 새로 만듭니다. 다른 서비스의 @Transactional 메서드를 호출했을 때 "독립적으로 커밋/롤백되길" 기대한다면 REQUIRES_NEW를 명시해야 합니다 — REQUIRED(기본값)는 호출자의 트랜잭션에 합쳐지므로, 호출자가 나중에 롤백하면 이미 호출한 메서드의 작업도 함께 롤백됩니다.
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void logAudit(String message) {
    // 바깥 트랜잭션이 롤백되어도 이 로그는 별도로 커밋되길 원할 때
}
  1. 자기 자신 호출(self-invocation) 문제도 함께 확인해야 합니다 — 같은 클래스 안에서 this.otherTransactionalMethod()로 호출하면 Spring AOP 프록시를 거치지 않아 @Transactional이 아예 적용되지 않습니다. 이 경우 메서드를 별도 빈으로 분리해야 합니다.

공식 문서

마지막 수정

좋아요북마크

댓글0

아직 댓글이 없어요. 첫 의견을 편하게 남겨 보세요.