JPA 예외 처리
JPA 표준 예외 정리
JPA 표준 예외들은 javax.persistence.PersistenceException의 자식 클래스다.
그리고 이 예외 클래스는 RuntimeException의 자식이다. 따라서 JPA 예외는 모두 언체크 예외다.
JPA 표준 예외는 크게 2가지로 나눌 수 있다.
- 트랜잭션 롤백을 표시하는 예외
- 트랜잭션 롤백을 표시하지 않는 예외
트랜잭션 롤백을 표시하는 예외는 심각한 예외이므로 복구해선 안 된다.
이 예외가 발생하면 트랜잭션을 강제로 커밋해도 트랜잭션이 커밋되지 않고 대신에 javax.persistence.RollbackException 예외가 발생한다. 반면에 트랜잭션 롤백을 표시하지 않는 예외는 심각한 예외가 아니다.
따라서 개발자가 트랜잭션을 커밋할지 롤백할지를 판단하면 된다.
스프링 프레임워크의 JPA 예외 변환
서비스 계층에서 데이터 접근 계층의 구현 기술에 직접 의존 한느 것은 좋은 설계라 할 수 없다. 이것은 예외도 마찬가지인데, 예를 들어 서비스 계층에서 JPA의 예외를 직접 사용하면 JPA에 의존하게 된다. 스프링 프레임워크는 이런 문제를 해결하려고 데이터 접근 계층에 대한 예외를 추상화해서 개발자에게 제공한다.
JPA 표준 명세상 발생할 수 있는 다음 두 예외도 추상화해서 제공한다.
스프링 프레임워크에 JPA 예외 변환기 적용
JPA 예외를 스프링 프레임워크가 제공하는 추상화 된 예외로 변경하려면 PersistenceExceptionTranslationPostProcessor를 스프링 빈으로 등록하면 된다. 이것은 @Repository 어노테이션을 사용한 곳 예외 변환 AOP를 적용해서 JPA 예외를 스프링 프레임워크가 추상화한 예외로 변환해준다.
// Bean 설정
@Bean
public PersistenceExceptionTranslationPostProcessor exceptionTranslation() {
return new PersistenceExceptionTranslationPostProcessor();
}
@Repository
public class NoResultExceptionTestRepository {
@PersistenceContext EntityManager em;
public Member findMember() {
return em.createQuery("select m from Member m", Member.class).getSingleResult();
}
}
NoResultExceptionTestRepository의 findMember는 조회한 결과가 없으면 javax.persistence.NoResultException이 발생한다. 이 예외가 findMember() 메서드를 빠져나갈 때 PersistenceExceptionTranslationPostProcessor에서 등록한 AOP 인터셉터가 동작해서 해당 예외를 org.springframework.dao.EmptyResultDataAccessException 예외로 변환해서 반환한다.
따라서 이 메소드를 호출한 클라이언트는 스프링 프레임워크가 추상화한 예외를 받는다.
만약 예외를 변환하지 않고 그대로 반환하고 싶으면 throws 절에 그대로 반환할 JPA 예외나 JPA 예외의 부모 클래스를 직접 명시하면 된다.
트랜잭션 롤백 시 주의사항
트랜잭션을 롤백하는 것은 데이터베이스의 반영사항만 롤백하는 것이지 수정한 자바 객체까지 원상태로 복구해주지는 않는다. 예를 들어 엔티티를 조회해서 수정하는 중에 문제가 있어서 트랜잭션을 롤백하면 데이터베이스의 데이터는 원래대로 복구되지만 객체는 수정된 상태로 영속성 컨텍스트에 남아있다. 따라서 트랜잭션이 롤백된 영속성 컨텍스트를 그대로 사용하는 것은 위험하다.
새로운 영속성 컨텍스트를 생성해서 사용하거나 ENtityManager.clear()를 호출해서 영속성 컨텍스트를 초기화한 다음에 사용해야 한다.
스프링 프레임워크는 이런 문제를 예방하기 위해 영속성 컨텍스트의 범위에 따라 다른 방법을 사용한다.
기본 전략인 트랜잭션 당 영속성 컨텍스트 전략은 문제가 발생하면 트랜잭션 AOP 종료 시점에 트랜잭션을 롤백하면서 영속성 컨텍스트도 함께 종료하므로 문제가 발생하지 않는다.
문제는 OSIV처럼 영속성 컨텍스트의 범위를 트랜잭션 범위보다 넓게 사용해서 여러 트랜잭션이 하나의 영속성 컨텍스트를 사용할 때 발생한다. 이때는 트랜잭션을 롤백해서 영속성 컨텍스트에 이상이 발생해도 다른 트랜잭션에서 해당 영속성 컨텍스트를 그대로 사용하는 문제가 있다. 스프링 프레임워크는 영속성 컨텍스트의 범위를 트랜잭션의 범위보다 넓게 설정하면 트랜잭션 롤백 시 영속성 컨텍스트를 초기화해서 잘못된 영속성 컨텍스트를 사용하는 문제를 예방한다.