@Transactional과 RuntimeException

이호석·2023년 7월 8일
0

@Transactional이 있는 메소드에 내가 사용자 지정 예외가 터졌는데, 예상했던 것과 달리 롤백이 되지 않아서 당황했던 적이 있다. 오늘은 트랜잭션을 지정한 메소드에 에러가 발생했는데 롤백이 되지않은 이유에 대해 알아보겠다.

예외 종류

  • CheckException : 컴파일러가 체크한 예외 - RuntimeException, Error를 제외한 나머지가 여기에 속한다. 예외 발생시 트랜잭션을 롤백하지 않고 예외를 던진다.
  • UncheckedException : 컴파일러가 체크하지 못한 예외 - RuntimeException, Error가 여기에 속한다. 예외 발생시 트랜잭션을 롤백한다.

롤백 in 트랜잭션

public interface MemberRepository extends JpaRepository<Member, Long> {
}

@Service
@Log4j2
@RequiredArgsConstructor
public class MemberService{
	private final MemberRepository memberRepository;
    
    @Transactional
    public void exceptionMethod() {
    	memberRepository.save(new Member());
        try {
            throw new Exception();
        }catch (Exception e){
            log.info("에러 발생");
        }
    }
}

위 코드를 실행했을 때 롤백이 발생할까? 위에서 알려줬듯 정답은 '발생하지 않는다'이다.

@Service
@Log4j2
@RequiredArgsConstructor
public class MemberService{
	private final MemberRepository memberRepository;
    
    @Transactional
    public void exceptionMethod() {
    	memberRepository.save(new Member());
        try {
            throw new RuntimeException();
        }catch (RuntimeException e){
            log.info("에러 발생");
        }
    }
}

그렇다면 위 코드는 어떨까? RuntimeException은 UncheckedException에 속하니 롤백이 발생해야할 것 같지만, 막상 실행 해보면 롤백되지 않는다. 이유는 try-catch문안에 있어서이다. RuntimeException이 발생했지만 try-catch문으로 예외를 확인하고 처리를 했기 때문이다.

@Service
@RequiredArgsConstructor
public class MemberService{
	private final MemberRepository memberRepository;
    
    @Transactional
    public void exceptionMethod() {
    	memberRepository.save(new Member());
        throw new RuntimeException();
    }
}

try-catch문이 없어지고 나서야 이제야 우리가 예상했던 트랜잭션 롤백이 실행된다. 좀 더 자세히 말하자면 예외가 발생했을 때 rollback-only 마킹을 하고, 최종 커밋을 할때 rollback-only가 마킹되어 있는것을 확인하고 롤백을 한다.

CheckedException도 롤백하고 싶다면?

CheckedException도 롤백하려면 여러가지 방법이 있다.

방법 1

@Transactional(rollbackOn = {Exception.class})

방법 1은 롤백하고 싶은 CheckedException을 명시해주는 것이다. @Transactional의 옵션 rollbackOn의 default는 Runtime과 Error만 되어있다. 이 값을 원하는 예외로 바꿔주기만 하면된다.

방법 2

...
try{
	...
} catch(Exception e){
	throw new ExampleException(e.getMessage());
} 

방법 2는 사용자 정의 에러를 발생시키는 것이다. 당연히 사용자 정의 에러는 RuntimeException을 상속 받아야 한다.

정리

아마 스프링은 UncheckedException은 말 그대로 개발자가 예상치 못한 에러라 판단해서 롤백하게 하는것 같다. 반면 CheckedException은 예상한 에러이니 굳이 롤백할 이유가 없다고 판단해서이고...
트랜잭션에서 RuntimeException에 대해 자세하게 쓰인 우아한기술블로그를 보는 것도 추천한다.

0개의 댓글