Spring Framework을 사용하면서 @Transactional 어노테이션에 대해 크게 고민해본 적이 없었다. 트랜잭션 내의 작업에 있어서 원자성을 보장해준다? 정도
(원자성: 하나의 원자 트랜잭션은 모두 성공하거나 또는 실패하는 데이터베이스 운용 집합)
오늘 회사에서 업무를 하다가 @Transactional이 선언 된 서비스 내 메서드 실행 시 예외가 발생하였는데 코드를 수정하고 다시 동작시켜보니 이미 데이터가 생성되었단다. DB를 확인해보니 데이터가 rollback되지 않고 들어가 있었다.
찾아보니 try내에서 예외 발생 시 catch 처리했을 경우 rollback이 되지 않는다고 한다. 그럼 try catch시 트랜잭션의 원자성은 보장받지 못하는 것일까? 그렇지도 않다.
Spring에서 @Transactional 선언 시 기본 속성은 아래와 같다.
@Transactional
//@Transactional(rollbackFor = {RuntimeException.class, Error.class}) 기본 속성
public void save(User user) {
userRepository.save(user);
}
즉, RuntimeException과 Error가 발생했을 경우에는 기본적으로 rollback이 된다는 것.
-> Exception과 Error에 대해서도 추후 자세히 다루어야겠다.
위 두 가지 경우가 아니라면 rollback이 되지 않는다. 하지만 트랜잭션을 rollback 하는 다른 방법들도 있다.
1. @Transactional의 옵션 값을 변경
@Transactional(rollbackFor = {Exception.class})
public void save(User user) {
userRepository.save(user);
}
위와 같이 작성하면 모든 예외에 대해서 rollback 하게 된다. 옵션을 변경해서 rollback이 가능하다는 것.
2. RuntimeException을 던짐
@Transactional(rollbackFor = {RuntimeException.class, Error.class})
public void save(User user) {
try {
userRepository.save(user);
} catch(Exception e) {
throw new RuntimeException("RuntimeException rollback");
}
}
RuntimeException을 던져주면 @Transactional의 기본 옵션에 해당하기 때문에 예외가 발생한 경우 rollback이 된다.
3. 수동으로 rollback
@Transactional(rollbackFor = {RuntimeException.class, Error.class})
public void save(User user) {
try {
userRepository.save(user);
} catch(Exception e) {
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
}
Spring Framework에 TransactionAspectSupport 클래스를 이용해 수동으로 rollback 해주는 방법이 있다. 위와 같이 작성하면 Exception 발생 시 해당 메서드를 rollback 시킬 수 있다.
** 추가적으로 특정 예외 발생 시 rollback이 되지 않도록 설정하는 방법
@Transactional(noRollbackFor = {RuntimeException.class})
public void save(User user) {
userRepository.save(user);
}
예외가 발생했을 경우에도 rollback을 하는 다양한 방법이 있지만, 어떻게 예외를 처리해 rollback 시키는 것이 좋은 방법인지에 대해서는 많은 고민이 필요할 것 같다.