@Transactional Rollback이 안되는 이유, @Transactional 사용시 주의 사항 - 삽질중인 개발자
개발을 하다 보니 예외(Exception)에 대해서 아무 생각 없이 사용하다가 심각한 버그를 발견하게 되어서 글을 정리한다.
아마 초보 개발자들이 가장 많이 하는 실수 중 하나 아닐까 싶다.
아래의 코드는 문자열을 넘겨주면 해당 문자열의 길이를 반환해주는 함수이다.
public int getLength(String str) throws Exception{
if(str == null){
throw new Exception();
}
return str.length();
}
그냥 봤을 때는 null일 때 유효성 검사도 되어 있고 유효성 검사에서 걸리면 Exception까지 던져주는 문제없어 보이는 코드처럼 보인다.
만약 위에 있는 함수를 사용해서 스프링에서 제공하는 @Transactional 어노테이션을 사용하여 아래와 같은 코드를 짠다고 가정하면 과연 DB에는 값이 저장되어 있을까? 아니면 롤백 처리가 되어 있을까??
@Transactional
public void something() throws Exception {
String str = "문자 1" ;
saveStr(str); // 문자를 DB에 저장하는 코드
String str2 = null;
int length = getLength(str2); // null 이기에 여기서 Exception이 발생한다.
saveStr(str2); // 문자를 DB에 저장하는 코드
}
정답은 saveStr까지는 저장이 된다.
분명 @Transactional을 사용해서 로직에 문제가 있으면 Rollback이 되도록 의도하여 코드를 작성했을 텐데 실제로 DB에는 "문자 1" 이라는 값이 저장되어 있다. 왜 "문자 1"이 저장이 되었을까?
위에 의문을 풀기 위해서는 우선 자바에서의 예외의 정의에 대해서 알아야 한다.
위에 포스팅을 읽고 왔다면 Checked Exception과 Unchecked Exception이 무엇인지 알 것이다.
그럼 이제 왜 DB가 롤백되지 않는지에 대해서 알아보자.
스프링 프레임워크에서 @Transactional 어노테이션은 기본적으로 Checked Exception에 대해서는 롤백시키지 않도록 설계되어 있다. 그 이유는 스프링 프레임워크가 EJB에서의 관습을 따르기 때문이라고 한다.
그렇기 때문에 위에 있는 코드가 throw new Exception(); 로 강제로 Checked Exception을 던져버리면서 롤백이 발생되지 않았던 것이다.
다행이게도 @Transactional 어노테이션의 rollbackFor 이라는 옵션을 사용하여 원하는 Exception에 대해서 롤백시켜버릴 수 있는 방법이 존재한다.
@Transactional(rollbackFor = {Exception.class})
하지만 이런 식으로 코딩을 하는 것보다 예외 전환을 통해서 롤백이 되도록 구현하는 게 더 좋아 보인다.