JPA를 사용하다 보면 NoResultException, NonUniqueResultException, EntityExistsException, OptimisticLockException, RollbackException 등 다양한 예외들이 발생할 수 있다. 이 때 우리는 어떤 자세를 취해야할까? 여러 관점 중 롤백 관점에서 알아보도록 하자.
JPA 표준 예외(JPA에서 발생하는)는 모두 언체크드 예외이며, javax.persistence.PersistenceException의 자식 클래스이다.
JPA 표준 예외는 '트랜잭션 롤백을 표시하는 예외'와 '트랜잭션 롤백을 표시하지 않는 예외'로 나눌 수 있다.
(롤백을 표시한다는 것은 트랜잭션 상태에 롤백을 해야하는지 안해도 되는지 기록을 해놓는 것이다. 커밋시에 트랜잭션 상태에 기록한 정보를 보고 커밋과 롤백 실행 여부가 갈리게 된다.)
트랜잭션 롤백을 표시하는 예외는 심각한 예외라고 판단된 예외들이다. 따라서 복구해선 안된다.
이 예외들이 발생하면 강제로 commit하려고 해도 javax.persistence.RollbackException이 발생한다.
- javax.persistence.EntityExistsException
- javax.persistence.EntityNotFoundException
- javax.persistence.OptimisticLockException
- javax.persistence.PessimisticLockException
- javax.persistence.RollbackException
- javax.persistence.TransactionRequiredException
반면에 트랜잭션을 롤백을 표시하지 않는 예외는 심각하지 않다고 판단된 예외다. 따라서 개발자가 커밋을 할지 롤백을 할지 결정하면 된다.
- javax.persistence.NoResultException
- javax.persistence.NonUniqueResultException
- javax.persistence.LockTimeoutException
- javax.persistence.QueryTimeoutException
(1. java version에 따라 javax는 jakarta로 표기될 수 있다.)
(2. 스프링부트 버전에 따라 해당 예외들은 스프링에 의해서 스프링 예외로 변환되어 던져질 수 있다.
ex. OptimisticLockException -> ObjectOptimisticLockingFailureException
PersistenceException -> JpaSystemException
NoResultException -> EmptyResultDataAccessException)
따라서 예외처리를 하고 싶을 때, 롤백을 표시하는 예외들은 예외처리를 하지 않는 것이 좋고, 롤백을 표시하지 않는 예외들은 편하게 예외처리를 하면 된다.
트랜잭션 롤백이 될 때 주의해야할 점이 있다.
트랜잭션 롤백은 DB에 날린 쿼리만 롤백하는 것이지. 수정한 자바 객체까지 원상태가 되진 않는다. 따라서 영속성 컨텍스트에는 수정된 객체가 남아있게 되고, 이는 정합성에 문제를 일으킨다.
그러므로 이 문제를 해결하기 위해선 롤백 후에는 새로운 영속성 컨텍스트를 생성해서 사용하거나, EntityManager.clear()를 통해 영속성 컨텍스트를 초기화한 다음에 사용해야 한다.
스프링에서는 이 처리를 자동으로 해주고 있고, 이는 영속성 컨텍스트의 범위에 따라 다른 방법이 사용된다.
1. 트랜잭션당 영속성 컨텍스트 (영속성 컨텍스트 범위 = 트랜잭션 범위)
2. 영속성 컨텍스트 범위 > 트랜잭션 범위
기본 전략인 1번의 경우, 트랜잭션 AOP 종료 시점에 트랜잭션 롤백을 하면서 영속성 컨텍스트도 함께 종료시켜서 문제가 발생하지 않게 해준다.
OSIV 같은 2번의 경우, 트랜잭션 롤백을 하면서 영속성 컨텍스트를 초기화(EntityManager.clear()) 시켜서, 문제가 발생하지 않게 해준다.
즉, 스프링을 사용하면 롤백 후에, 영속성 컨텍스트에 잘못된 데이터가 들어가 있을지 걱정하지 않아도 된다.
다만 자바객체에는 수정된 값으로 존재하고 있기 때문에 그 객체를 어떻게 처리할지는 개발자가 판단해야 한다.
정리해보자.
JPA 표준예외는 언체크드 예외이기 때문에 코드를 작성할 때, 명시적으로 예외처리를 해주지 않아도 된다.(예외처리를 하지 않는다면, 스프링 AOP에서 처리한 방법대로 처리될 것이다.) 하지만 예외처리를 해주고 싶다면 어떤 점을 염두에 두어야 할까. 롤백 여부이다.
트랜잭션이 시작된 상태에서 트랜잭션 롤백을 표시하는 예외가 발생하면, 그대로 두어서 롤백을 하도록 두는 것이 좋다.
예외처리를 할 경우. 하이버네이트를 이용한다면, 영속성 컨텍스트를 반드시 새로 만들거나 초기화 해주어야 정합성에 문제가 없다. 스프링 jpa를 사용한다면 영속성 컨텍스트 문제는 스프링이 알아서 해주기 때문에, 이후 로직을 작성하면 된다.
트랜잭션이 시작된 상태에서 트랜잭션 롤백을 표시하지 않는 예외가 발생하면, 예외처리를 할 때, 롤백을 하건 커밋을 하건 개발자의 판단에 따르면 된다. 이 예외는 데이터 정합성과 관련이 없으므로, 영속성 컨텍스트는 신경 쓰지 않아도 된다.
발생하는 예외는 기본적으로는 JPA표준예외가 발생하지만, 스프링 부트 버전에 따라 스프링 예외로 변환되어 던져질 수 있다.
(참고. PersistenceExceptionTranslationPostProcessor, PersistenceExceptionTranslationInterceptor, EntityManagerFactoryUtils, HibernateJpaDialect)
ref. 자바 ORM 표준 JPA 프로그래밍 김영한 저.
'막쓰기' 카테고리의 다른 글
좋은 설계를 위해 생각해보기 (0) | 2022.07.06 |
---|---|
Enum (0) | 2022.07.01 |