JPA는 여러가지 Exception을 제공한다. 그리고 해당 Exception은 PersistenceException을 상속하기 때문에 모두 PersistenceException의 자식이다. 참고로 PersistenceException은 RuntimeException을 상속하여 정의되었다.
Persistence Exception 과 Transaction
JPA Exception은 크게 두가지로 나뉠 수 있다.
- 트랜잭션의 롤백을 표시하는 Exception
- 트랜잭션의 롤백을 표시하지않는 Exception
트랙잭션을 롤백을 표시한다는 것은 간단하게 말하자면 스프링에서 트랜잭션을 사용하려고할 때 내부 쓰레드가 rollback only mark가 되어있는지 확인하고 안되어 있다면 정상적으로 비지니스 로직을 처리하고 되어있지 않는다면 롤백하는 구조가 롤백 표시이다. 해당 쓰레드에 롤백 마크가 생기면 롤백을 바꿀 수도 강제로 트랜잭션을 처리할 수 도 없기 때문에 주의하여야 한다.
롤백
예외 | 설명 |
javax.persistence.EntityExistsException | EntityManager.persist(...) 호출 시 이미 같은 엔티티가 있으면 발생 |
javax.persistence.EntityNotFoundException | EntityManager.getReference(...)를 호출했는데 실제 사용 시 엔티티가 존재하지 않으면 발생. refresh(), lock() 에서도 발생 |
javax.persistence.OptimisticLockException | 낙관적 락 충돌 시 발생 |
javax.persistence.PessimisticLockException | 비관적 락 충돌 시 발생 |
javax.persistence.RollbackException | EntityTransaction.commit() 실패 시 발생. 롤백이 표시되어 있는 트랜젝션 커밋 시에도 발생 |
javax.persistence.TransactionRequiredException | 트랜잭션이 필요할 때 트랜젝션이 없으면 발생. 트랜잭션 없이 엔티티를 변경할 때 주로 발생 |
롤백 X
예외 | 설명 |
javax.persistence.NoResultException | Query.getSingleResult() 호출 했는데 결과가 없을 때 반환 |
javax.persistence.NonUniqueResultException | Query.getSingleResult() 호출 했는데 결과가 2 이상일 때 반환 |
javax.persistence.LockTimeoutException | 비관적 락에서 시간 초과 시 발생 |
javax.persistence.QueryTimeoutException | 쿼리 실행 시간 초과 시 발생 |
JPA Excepion 변환
서비스 레이어에서 데이터 접근 계층에서 사용되는 JPQL, SQL, JDBC을 사용하지 않는 이유는 비지니스 로직이 데이터베이스, Entity의 의존하게 되게 됨으로써 변경이 일어날 때마다 참조하고 있는 모든 비지니스 로직을 수정하고 어떠한 버그가 일어날지 모르게 된다. 따라서 데이터 접근 모듈을 분리시키는 이유는 의존성을 줄이고 변경에 의한 이펙트를 최소화 하기 위함이다.
JPA Exception도 마찬가지로 직접 가져다 쓰게 되면 JPA의 버전이 변경되거나 구조가 바뀔 때마다 의존하는 문제가 발생하기 때문에 스프링의 Exception으로 변환하여 사용할 수 있도록 제공합니다.
예시
Exception | 변환 |
javax.persistence.PersistenceException | org.springframework.orm.jpa.JpaSystemException |
javax.persistence.NoResultException | org.springframework.dao.EmptyResultDataAccessException |
예제
변환하고자 하는 PersistenceExceptionTranslationPostProcessor를 빈으로 등록합니다. (xml 또는 어노테이션 등)
@Configuration
public class ExceptionConfiguration {
@Bean
public PersistenceExceptionTranslationPostProcessor exceptionTranslation(){
return new PersistenceExceptionTranslationPostProcessor();
}
}
PersistenceExceptionTranslationPostProcessor의 역활
빈으로 등록된 레파지토리에서 발생하는 Persistence 예외들을 DataAccessException(데이터를 접근하는 예외의 루트 클래스)으로 변경 시켜주는 역활을 합니다.
Translates native resource exceptions to Spring's DataAccessException hierarchy. Autodetects beans that implement the PersistenceExceptionTranslator interface, which are subsequently asked to translate candidate exceptions.
참조
ExceptionRepository
@Repository
public class ExceptionRepository {
@PersistenceContext
private EntityManager em;
public Member findMember() {
return em.createQuery("select m from Member m", Member.class).getSingleResult();
}
}
Test
@SpringBootTest
@ExtendWith(SpringExtension.class)
class ExceptionRepositoryTest {
@Autowired
private ExceptionRepository exceptionRepository;
@Test
@DisplayName("예외 변환 테스트")
public void exceptionTest() {
exceptionRepository.findMember();
}
}
동작 구조
- findMember()를 호출
- getSingleResult()를 통해 값을 가져올 때 JPA는 내부에서 javax.persistence.NoResultException을 발생
- 메서드를 빠져나가면서 PersistenceExceptionTranslationPostProcessor에서 등록된 aop 인터셉터가 동작
- EmptyResultDataAccessException로 변환하여 리턴한다.
public Member findMember() throws javax.persistence.PersistenceException {
return em.createQuery("select m from Member m", Member.class).getSingleResult();
}
하지만 필요에 따라서 그냥 JPA Exception을 활용해야 한다면 위처럼 예외를 던지면 @Repository 바깥으로 전달되기 때문에 변환되지않고 예외를 발생시킵니다.
트랜잭션 롤백시 주의 사항
- 롤백은 데이터베이스 사항에 대해서만 롤백하기 때문에 영속성 컨텍스트에 있는 수정된 정보들은 그대로 남아있다.
- 영속성 컨텍스트를 비우지 않는다면 데이터의 무결성 문제가 발생할 수 있기 때문에 EntityManager.clear() 통해 비워야 한다.
- 스프링 프레임워크는 이런 문제를 예방하기 트랜젝션 AOP 종료 시점에 트랜젝션을 롤백하면서 영속성 컨텍스트도 함께 종료하기 때문에 문제가 되지 않는다.
- OSIV 처럼 영속성 컨텍스트의 범위가 트랜잭션 범위보다 넓게 살려두는 경우 문제가 될 수 있는데 이 설정을 사용할 때 rollback 이 일어나면 스프링은 영속성 컨텍스트의 EntityManager.doRollback() or clear() 를 호출해서 이 문제를 예방한다.
참고: EntityManger.doRollback()은 finally를 통해여 엔티티매니저를 비우기 때문에 같이 사용해도 문제가 없다.
'JAVA > JPA' 카테고리의 다른 글
프록시 심화 (0) | 2022.11.26 |
---|---|
JPA의 엔티티 비교 (0) | 2022.11.26 |
<JPA> 네이티브 SQL 사용 (0) | 2022.10.22 |
<JPA> Criteria의 사용 방법 (0) | 2022.10.22 |
복합키와 식별관계 (1) | 2022.09.24 |