Hibernate의 ConstraintViolationException는 Translate 된다

공병주(Chris)·2023년 3월 9일
0
post-thumbnail

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의 조건문 내부)

profile
self-motivation

0개의 댓글