Skip to content

exception translation in spring-data

참고 자료

  • 토비의 스프링 3.1 Vol1 스프링의 이해와 원리 4장 2절 예외 전환

1. Let's Read Javadoc of @Repository!

org.springframework.stereotype.Repository - javadoc
지정된 클래스가 DDD에서의 "Repository" 임을 명시한다.

팀에 따라 전통적인 Java EE 패턴에서의 DAO 를 의미할 수도 있다.
물론 두 패턴에서 의미하는 영속성 접근에 대한 개념 차이를 잘 이해해야 한다.

PersistenceExceptionTranslationPostProcessor 와 함께 사용되는 경우 DataAccessException 으로의 예외 변환을 담당한다. 다른 Stereotype 애너테이션과 마찬가지로 전체적인 애플리케이션 아키텍쳐 내에서의 aspect 로서 동작할 수도 있다.

Spring 2.5 부터 이 애너테이션은 @Component 의 meta annotation 으로 취급되었다.

아주 흥미로운 이야기가 많다. 이번 시간에는 세번째 문단에서 소개하는 예외 변환 기능에 대해서 알아보자.

2. PersistenceExceptionTranslationPostProcessor

  • @Repository 가 적용된 클래스에 PersistenceExceptionTranslationAdvisor 를적용하는 빈 후처리를 한다.

해당 Advisor 의 advice 인 PersistenceExceptionTranslationInterceptor 의 invoke ``메서드

@Override  
@Nullable  
public Object invoke(MethodInvocation mi) throws Throwable {  
   try {  
      return mi.proceed();  
   }  
   catch (RuntimeException ex) {  
      // Let it throw raw if the type of the exception is on the throws clause of the method.  
      if (!this.alwaysTranslate && ReflectionUtils.declaresException(mi.getMethod(), ex.getClass())) {  
         throw ex;  
      }  
      else {  
         PersistenceExceptionTranslator translator = this.persistenceExceptionTranslator;  
         if (translator == null) {  
            Assert.state(this.beanFactory != null,  
                  "Cannot use PersistenceExceptionTranslator autodetection without ListableBeanFactory");  
            translator = detectPersistenceExceptionTranslators(this.beanFactory);  
            this.persistenceExceptionTranslator = translator;  
         }  
         throw DataAccessUtils.translateIfNecessary(ex, translator);  
      }  
   }  
}

계속 타고 들어가다 보면 jpa/hibernate 를 사용하는 경우 실질적인 예외 변환이 일어나는 곳을 찾을 수 있다.

org.springframework.orm.jpa.EntityManagerFactoryUtils.convertJpaAccessExceptionIfPossible
@Nullable  
public static DataAccessException convertJpaAccessExceptionIfPossible(RuntimeException ex) {  
   // Following the JPA specification, a persistence provider can also  
   // throw these two exceptions, besides PersistenceException.   if (ex instanceof IllegalStateException) {  
      return new InvalidDataAccessApiUsageException(ex.getMessage(), ex);  
   }  
   if (ex instanceof IllegalArgumentException) {  
      return new InvalidDataAccessApiUsageException(ex.getMessage(), ex);  
   }  

   // Check for well-known PersistenceException subclasses.  
   if (ex instanceof EntityNotFoundException) {  
      return new JpaObjectRetrievalFailureException((EntityNotFoundException) ex);  
   }  
   if (ex instanceof NoResultException) {  
      return new EmptyResultDataAccessException(ex.getMessage(), 1, ex);  
   }  
   if (ex instanceof NonUniqueResultException) {  
      return new IncorrectResultSizeDataAccessException(ex.getMessage(), 1, ex);  
   }  
   if (ex instanceof QueryTimeoutException) {  
      return new org.springframework.dao.QueryTimeoutException(ex.getMessage(), ex);  
   }  
   if (ex instanceof LockTimeoutException) {  
      return new CannotAcquireLockException(ex.getMessage(), ex);  
   }  
   if (ex instanceof PessimisticLockException) {  
      return new PessimisticLockingFailureException(ex.getMessage(), ex);  
   }  
   if (ex instanceof OptimisticLockException) {  
      return new JpaOptimisticLockingFailureException((OptimisticLockException) ex);  
   }  
   if (ex instanceof EntityExistsException) {  
      return new DataIntegrityViolationException(ex.getMessage(), ex);  
   }  
   if (ex instanceof TransactionRequiredException) {  
      return new InvalidDataAccessApiUsageException(ex.getMessage(), ex);  
   }  

   // If we have another kind of PersistenceException, throw it.  
   if (ex instanceof PersistenceException) {  
      return new JpaSystemException(ex);  
   }  

   // If we get here, we have an exception that resulted from user code,  
   // rather than the persistence provider, so we return null to indicate   // that translation should not occur.   
   return null;  
}

3. DataAccessException Hierarchy

위 예외 변환 코드를 살펴보면 반환 예외는 모두 DataAccessException 의 하위 타입임을 알 수 있다.DataAccessException 는 회복 가능성을 기준으로 크게 세가지로 구분된다.

  • abstract TransientDataAccessException - 재시도 하면 성공 가능성 있음
  • abstract NonTransientDataAccessException - 재시도 해도 실패함
  • non-abstract RecoverableDataAccessException - recovery operation 을 포함한 경우 활용하는 예외

Last update: March 23, 2023
Created: March 23, 2023