[이슈해결] UnexpectedRollbackException 발생

MinSeong Kang·2022년 8월 10일
0

이슈해결

목록 보기
5/12

지금까지 @Transaction 어노테이션을 사용하면서, Repository 혹은 Service 클래스 중 한 곳만 @Transaction 어노테이션을 달아 트랜잭션 전파에 대해서 알지 못했다.

트랜잭션 전파

이번에 트랜잭션 중에 다른 트랜잭션이 동작하려고 할 때 어떤일이 일어나는지 스프링 트랜잭션 전파에 대해서 공부하게 되었다.
트랜잭션 내 다시말해 @Transactional 어노테이션이 붙어 있는 메서드 내에 내부 메서드가 있을 수 있다. 또한 해당 내부 메서드는 @Transactional 어노테이션이 붙어 있을 수 있다. 이 때 고려해야하는 부분이 스프링 트랜잭션 전파이다.!!
아래가 다음과 같은 예제이다.

// Service
...
@Transactional
public void join(String username) {
  Member member = new Member(username);
  Log logMessage = new Log(username);

  memberRepository.save(member);
  logRepository.save(logMessage);
}
@Repository
@RequiredArgsConstructor
public class MemberRepository {

    private final EntityManager em;
    
    @Transactional
    public void save(Member member) {
        em.persist(member);
    }
    
    ...
}
@Repository
@RequiredArgsConstructor
public class LogRepository {

    private final EntityManager em;
    
    @Transactional
    public void save(Log logMessage) {
        em.persist(logMessage);
    }
    ...
}

join() → 외부 트랜잭션 // memberRepository.save(member); logRepository.save(logMessage); → 내부 트랜잭션
트랜잭션 내에서 체크 예외 발생 → 커밋 // 런타임 예외 발생 → 롤백

  • 먼저 @Transactional의 propogation의 default는 Propagation.REQUIRED 이다. 이는 기존 트랜잭션이 없으면 생성하고, 있으면 기존에 존재하는 트랜잭션에 참여한다는 의미이다.
    따라서 해당 코드에서 memberRepository.save(member); logRepository.save(logMessage); 모두 join()이 시작했을 때 생성한 트랜잭션에 참여하여 처리된다.
  • 그렇다면 만일 2개의 내부 트랜잭션 중 하나라도 롤백이 되면 어떻게 될까? 3개의 작업중 하나라도 롤백이 일어나면 해당 트랜잭션은 전체가 롤백된다. 그러면서 아래와 같은 오류가 UnexpectedRollbackException이 발생한다.
  • 내부 트랜잭션이 롤백되면 트랜잭션 동기화 매니저에게 rollback-only 를 마크한다. 따라서 외부 트랜잭션이 커밋되더라도 트랜잭션 동기화 매니저를 체크하여 rollback-only 마크로 인해 롤백 처리한다.

해당 전파 방식을 통해 트랜잭션의 동기화를 할 수 있다. 하지만 만일 내부 트랜잭션이 커밋되든 롤백되든 상관없이 외부 트랜잭션이 커밋되기를 바랄 상황이 있을 것이다. 이는 @Transactional의 propogation 설정값중 Propagation.REQUIRES_NEW 로 설정하면 된다. 이는 기존 트랜잭션(외부 트랜잭션)이 있든 말든 새로운 트랜잭션이 생성되어, 기존 트랜잭션과 독립적인 트랜잭션을 만들어 로직을 수행시킬 수 있다.

결론!

  • 트랜잭션 내에서 다른 트랜잭션이 동작할 때 스프링 트랜잭션 전파 옵션에 따라 다른 결과가 나온다.
  • Propagation.REQUIRED 라면, 내부 트랜잭션이 외부 트랜잭션에 합류되어 수행, 내부 트랜잭션이 롤백되고 외부 트랜잭션이 커밋될 때 UnexpectedRollbackException 발생하여 해당 트랜잭션은 롤백 된다.
  • 내부 트랜잭션이 외부 트랜잭션과 독립적으로 트랜잭션을 수행시키고 싶다면, 전파 옵션을 Propagation.REQUIRES_NEW 로 변경하여 수행, 내부 트랜잭션은 기존 트랜잭션의 영향을 받지 않고 새로운 트랜잭션을 획득하여 롤백되더라도 외부 트랜잭션에 영향을 주지 않는다.

참고자료

https://www.inflearn.com/course/스프링-db-2/dashboard
https://techblog.woowahan.com/2606/

0개의 댓글