Written by
Qoo
on
on
[ISSUE] Spring transactional rollBackException
Spring transactional rollBackException
서버 운영중에 이런 에러가 갑자기 발생하기 시작했습니다.
에러정보
org.springframework.transaction.HeuristicCompletionException : Heuristic completion: outcome state is rolled back; nested exception is org.springframework.transaction.TransactionSystemException: Could not commit JPA transaction; nested exception is javax.persistence.RollbackException: Transaction marked as rollbackOnly
재현
@Transactional
public class UserApplication {
@Autowired
private UserService userService;
public void saveUser(){
if(userService.isNotExistUser(1L)) {
userService.saveUser(new User());
}
}
}
유저가 없을때만 저장하는 코드가 있다고 하자.
@Slf4j
@Transactional
public class UserService {
@Autowired
private UserRepository userRepository;
public boolean isNotExistUser(Long userId){
try{
throw new NullPointerException("없음");
} catch (Exception e){
log.info("user is null");
return true;
}
}
public void saveUser(User user){
userRepository.save(user);
}
}
isNotExistUser 에서는 NullPointerException 을 통해서 boolean 값을 return 한다고 가정했다.
이렇게 처리를 하게되면 재현을 할 수 있다.
이런 에러는 왜 발생하게 되는것인가?
원인 분석
기본적으로 @Transactional Propagation 은 TransactionDefinition.PROPAGATION_REQUIRED 가 default 이다.
이 속성은 미리 시작된 Transaction 이 있다면 참여하고, 미리 시작된 Transaction 이 없다면 새로 시작한다.
그렇기 문에 UserService 에서는 UserApplication 에서 시작한 Transaction 을 그대로 사용하게 된다.
UserService 의 isNotExistUser 에서 NullPointerException 이 발생하면서 UserApplication 에서 시작한
Transaction 은 rollBackOnly 로 설정된다. (RuntimeException 일때 Spring은 Rollback을 실행한다.)
하지만 Exception 을 먹어버리게 코딩이 되어있었기 때문에 문제없이 코드는 계속 진행된다.
하지만 user 를 save 하고 Transaction 을 종료하고 commit 하려고 했지만, Transaction 은 rollBackOnly 상태이기 때문에 Exception 이 발생한 것이다.