@Transactional 에 대한 것

2025. 8. 8. 17:39·Spring Framework/Spring

이번 글에서는 @Transactional 사용 시 유의해야 할 사항 중 특히 조용한 롤백 현상에 대해 이야기합니다.
겉으로는 정상 처리된 것처럼 보이지만 실제로는 데이터가 커밋되지 않는 상황이 발생할 수 있습니다.
왜 이런 일이 일어나는지 그리고 이를 어떻게 예방할 수 있는지 살펴봅니다.


이커머스, SNS의 "좋아요 수" 등록 기능의 멱등성 처리 설계 

"좋아요 등록은 멱등해야 한다."라는 요구사항을 개발하기 위해 DB 유니크 제약을 사용했다. 그래서 아래 단계로 생각하고 코드를 작성했다.
1. 좋아요 등록이 되어 있으면 익셉션이 발생할 것이다.
2. 해당 익셉션을 잡아서 비즈니스 익셉션을 발생하게 하면 호출부에서 처리할 수 있겠지

//LikeUseCase
@Transactional
public LikeResult.LikeRegisterResult register(final Long userId, final Long productId) {
    //상품조회
    Product prodcut = productService.get(id);

    try {
        //좋아요 등록
        likeService.register(userId, productId);
    } catch (CoreException e) {
        // 중복 요청으로 판단
        return LikeResult.LikeRegisterResult.duplicated(userId, productId);
    }

    // 좋아요 수 증가
    productService.increaseLikeCountAtomically(product);
    return LikeResult.LikeRegisterResult.newCreated(userId, productId);
}

//LikeService
public LikeHistory register(final Long userId, final Long productId) {
    final LikeHistory likeHistory;
    try {
        likeHistory = likeHistoryRepository.save(LikeHistory.from(userId, productId));
    }catch (DataIntegrityViolationException e){
        throw new CoreException(ErrorType.CONFLICT,"이미 좋아요를 등록한 상품입니다.");
    }
    return likeHistory;
}

 

유니크 제약을 이용한 멱등성 처리 과정에서 발생한 에러

 

그러나, 원하는대로 동작하지 않고 조용한 롤백이라는 에러 메세지가 돌아왔다.

Transaction silently rolled back because it has been marked as rollback-only org.springframework.transaction.UnexpectedRollbackException: Transaction silently rolled back because it has been marked as rollback-only

해석하면, 트랜잭션이 rollback-only로 표시되었기 때문에 조용히 롤백되었고, 이미 롤백 전용 상태여서
UnexpectedRollbackException이 발생했다는 것.

  1. 내부 트랜잭션에서 예외가 발생 → Spring이 해당 트랜잭션을 rollback-only 상태로 표시.
  2. 외부 트랜잭션에서 예외를 catch하고 정상적으로 진행하려 함 → 커밋 시도.
  3. 하지만 rollback-only 상태인 트랜잭션은 커밋할 수 없으므로, Spring이 UnexpectedRollbackException 을 던짐.

위 에러 메세지에서 난 "그래 난 커밋을 원하지 않아. 롤백을 원해!" 라고 생각했다.
그런데  여기서 말하는 롤백/커밋은 db 의 것이 아닌 것 같다. Springframework 의 transaction 같다.
@Transactional 이 붙어 있으면 트랜잭션 매니저가 "트랜잭션을 끝낸다." 라는 것을 처리해야 하는데 그게 안되게 마킹 처리해둔 것이고 그래서 메서드가 정상 실행이 되지 않는다고 생각했다.

근데 이걸 생각만 하는 것이 아니라 실제 스프링 코드로 확인해 보려면 어떻게 해야 하는 걸까. 이건 아직 궁금증으로 남아 있는 상태이다.

 


문제를 해결한 방법

 

당면한 문제를 해결하기 위해 조사한 내용은 다음과 같다.
조사한 내용은 catch문에 진입했다면 코드대로 return이 실행되어야 하는 것 아닌가 하는 부분이었다.


JPA/DB 레벨 예외(제약 위반 및 flush 시점 오류) 특징

  • save()/flush()/커밋 타이밍에 DB 제약 위반(유니크 키 등)이나 PersistenceException, DataIntegrityViolationException이 나면 하이버네이트가 현재 트랜잭션을 rollback-only로 마킹합니다.
  • 네가 바깥에서 그 예외를 catch 해서 “정상처럼” 리턴해도, 이미 트랜잭션 상태는 커밋 불가로 바뀐 상태.
  • 마지막 커밋 시점에 스프링이 “이건 롤백-only인데 커밋하려고 하네?” 하고 UnexpectedRollbackException을 던집니다.

즉, catch문은 분명히 실행되었지만 그럼에도 커밋을 시도하는 순간 발생한 예외가 바로 "조용한 롤백"입니다.

 

선택한 해결 방법

  1. 예외 자체를 피하기: 저장 전에 existsBy... 로 중복/제약 조건 선확인
  2. 트랜잭션 분리: inner() 쪽을 @Transactional(propagation = REQUIRES_NEW)로 분리
    → 내부 실패는 내부에서만 롤백되고, 바깥 트랜잭션은 정상 커밋 가능하도록 트랜잭션을 분리

애초에 좋아요 등록 여부 현황 조회 없이 저장을 시도하고, DB의 유니크 제약 조건을 통해 멱등성을 판단하려고 했기 때문에 2번 트랜잭션 분리 방법을 선택했습니다.
즉, 내부 호출 메서드가 별도의 트랜잭션을 생성하도록 설정했으며 따라서 내부에서 실패하더라도 외부 트랜잭션에는 영향을 주지 않도록 했습니다.

 

 

 

 

저작자표시 비영리 변경금지 (새창열림)

'Spring Framework > Spring' 카테고리의 다른 글

Spring @Transactional 동작, 로그로 확인하기  (0) 2025.08.24
Spring에서 Redis 사용하기 (RedisTemplate)  (3) 2025.08.17
스프링이 자바 빈 등록하는 방법  (1) 2025.06.07
Spring Event 를 활용한 예약 푸쉬 발송  (0) 2023.04.05
Spring Security 디버깅 방법  (0) 2023.03.30
'Spring Framework/Spring' 카테고리의 다른 글
  • Spring @Transactional 동작, 로그로 확인하기
  • Spring에서 Redis 사용하기 (RedisTemplate)
  • 스프링이 자바 빈 등록하는 방법
  • Spring Event 를 활용한 예약 푸쉬 발송
devstep
devstep
웹 백엔드 개발자
  • devstep
    개발 여정
    devstep
  • 전체
    오늘
    어제
    • 분류 전체보기 (91) N
      • Java (24)
      • Spring Framework (17) N
        • Spring (14) N
        • JPA (3)
      • Database (8)
        • RDBMS공통 (1)
        • MySQL (6)
        • Redis (0)
        • Oracle (1)
      • Concept (13)
        • 테스트코드 (4)
        • 클린코드 (2)
        • 성능테스트 (4)
        • 설계 (1)
        • 인증 (1)
        • REST API (1)
      • git (2)
      • Intellij (4)
      • Computer Science (3)
        • 네트워크 (1)
        • 자료구조 (1)
        • 보안 (1)
      • Essay (18) N
        • Learning Essay (10)
        • WIL (8) N
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    부하테스트도구
    component
    부하테스트
    테스트코드
    자바메모리모델
    JMeter
    ClusteredIndex
    인텔리제이실행에러
    springsecurity
    linux
    보안
    단위테스트
    대칭암호화
    bean
    JavaMemoryModel
    nginx
    비대칭암호화
    클린코드
    storageEngine
    JVM
    tdd
    nofile
    DDD
    성능테스트
    seed
    aggregate
    applicationcontext
    블록암호화
    innodb
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.4
devstep
@Transactional 에 대한 것
상단으로

티스토리툴바