2023 글로벌미디어학부 졸업 작품 프로젝트 Dandi를 개발하면서 ConstraintViolationException가 catch 되지 않아서 겪었던 문제에 대한 내용입니다.
JPA에서 unique 제약 조건에 위배된다면 hibernate.exception.ConstraintViolationException는 것으로 알고 있었습니다.
@Transactional
public void updateNickname(Long memberId, NicknameUpdateCommand nicknameUpdateCommand) {
Member member = findMember(memberId);
try {
memberPersistencePort.updateNickname(member.getId(), nicknameUpdateCommand.getNickname());
} catch (ConstraintViolationException e) {
throw new IllegalArgumentException("이미 존재하는 닉네임입니다.");
}
}
하지만 위처럼, update 쿼리를 날렸을 때 Unique 제약조건에 위배되어 ConstraintViolationException가 발생해도 ConstraintViolationException로 catch 할 수 없습니다.
@Transactional
public void updateNickname(Long memberId, NicknameUpdateCommand nicknameUpdateCommand) {
Member member = findMember(memberId);
try {
memberPersistencePort.updateNickname(member.getId(), nicknameUpdateCommand.getNickname());
} catch (RuntimeException e) {
logger.debug(e.getClass().getName());
throw new IllegalArgumentException("이미 존재하는 닉네임입니다.");
}
}
따라서, 로그를 통해 어떤 타입의 예외가 전달되는 확인해보았습니다.
결론부터 말하자면, DataIntegrityViolationException
이 발생합니다. 하지만, message에는 아래와 같이 출력됩니다.
could not execute statement; SQL [n/a]; constraint [member.UK_hh9kg6jti4n1eoiertn2k6qsc]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement
여기서 ConstraintViolationException이 발생하고, 예외 메시지를 가공해서 DataIntegrityViolationException의 생성자로 넣어준다
는 것을 추측할 수 있었습니다.
따라서, 생성자부터 브레이크 포인트를 찍어면서 따라 올라가보니, org.springframework.dao.support.PersistenceExceptionTranslationInterceptor
에서 예외가 번역됨을 알 수 있었습니다.
AOP Alliance MethodInterceptor that provides persistence exception translation based on a given PersistenceExceptionTranslator.
Delegates to the given PersistenceExceptionTranslator to translate a RuntimeException thrown into Spring's DataAccessException hierarchy (if appropriate). If the RuntimeException in question is declared on the target method, it is always propagated as-is (with no translation applied).
자세한 코드를 보시려면 아래의 순서로 살펴보시면 좋을 것 같습니다.
PersistenceExceptionTranslationInterceptor
의 invoke 메서드와 Line 152
ChainedPersistenceExceptionTranslator
의 translateExceptionIfPossible 메서드와 Line 61
HibernateJpaDialect
의 convertHibernateAccessException 메서드 Line 273의 조건문 내부)