본문 바로가기

Web/spring

[Spring Exception] Spring DataIntegrityViolationException

728x90

https://www.baeldung.com/spring-dataIntegrityviolationexception

 

 

 

 

 

Spring org.springframework.dao.DataIntegrityViolationException 

 

 

 

 


DataIntegrityViolationException and Spring Exception Translation

Spring 예외 변환 메커니즘은 컨텍스트에서 예외 변환 빈 포스트 프로세서 빈을 정의하여 @Repository 로 주석이 달린 모든 빈에 적용할 수 있다.

 

 

<bean id="persistenceExceptionTranslationPostProcessor" 
   class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor" />

 

@Configuration
public class PersistenceHibernateConfig{
   @Bean
   public PersistenceExceptionTranslationPostProcessor exceptionTranslation(){
      return new PersistenceExceptionTranslationPostProcessor();
   }
}

예외 변환 메커니즘은 Spring에서 사용할 수 있는

이전 지속성 템플릿(HibernateTemplate, JpaTemplate 등)에서도 기본적으로 활성화된다.

 

 


Where Is DataIntegrityViolationException Thrown

 

DataIntegrityViolationException with Hibernate

Spring Hibernate가 구성되면 Spring - SessionFactoryUtils - convertHibernateAccessException 에서 exception translation layer provided 예외 발생된다.

 

 

DataIntegrityViolationException을 발생 시킬 수 있는 세 가지 가능한 Hibernate 예외

  • org.hibernate.exception.ConstraintViolationException
  • org.hibernate.PropertyValueException
  • org.hibernate.exception.DataException

 

DataIntegrityViolationException With JPA

Spring이 지속성 공급자로 JPA로 구성되면 Hibernate와 유사하게 DataIntegrityViolationException이 예외 변환 계층(즉, EntityManagerFactoryUtils에서 - convertJpaAccessExceptionIfPossible) 에서 발생한다.

 

DataIntegrityViolationException 이 발생하도록 트리거할 수 있는 단일 JPA 예외( javax.persistence.EntityExistsException )가 있다.

 

 


Cause: org.hibernate.exception.ConstraintViolationException

DataIntegrityViolationException이 발생하는 가장 일반적인 원인이다.

Hibernate ConstraintViolationException 은 작업이 데이터베이스 무결성 제약 조건을 위반했을 때 나타난다.

@Test(expected = DataIntegrityViolationException.class)
public void whenChildIsDeletedWhileParentStillHasForeignKeyToIt_thenDataException() {
   Child childEntity = new Child();
   childService.create(childEntity);

   Parent parentEntity = new Parent(childEntity);
   service.create(parentEntity);

   childService.delete(childEntity);
}

부모 엔터티는 자식 엔터티 에 대한 외래 키를 가지고 있으므로 자식을 삭제하면 부모에 대한 외래 키 제약 조건이 깨져 DataIntegrityViolationException에서 Spring  의해 래핑된 ConstraintViolationException 이 발생하게 된다.

org.springframework.dao.DataIntegrityViolationException: 
could not execute statement; SQL [n/a]; constraint [null]; 
nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement
    at o.s.orm.h.SessionFactoryUtils.convertHibernateAccessException(SessionFactoryUtils.java:138)
Caused by: org.hibernate.exception.ConstraintViolationException: could not execute statement

 

문제를 해결하기 위해 부모를 먼저 삭제하도록 순서를 변경한다.

@Test
public void whenChildIsDeletedAfterTheParent_thenNoExceptions() {
   Child childEntity = new Child();
   childService.create(childEntity);

   Parent parentEntity = new Parent(childEntity);
   service.create(parentEntity);

   service.delete(parentEntity);
   childService.delete(childEntity);
}

 

 

 

 

 

 

Cause: org.hibernate.PropertyValueException

DataIntegrityViolationException 의 보다 일반적인 원인 중 하나로 Hibernate에서 이는 엔티티가 문제와 함께 지속되는 경우 나타난다. 엔터티에 null이 아닌 제약 조건 으로 정의된 null 속성이 있거나 엔터티 연결이 저장되지 않은 임시 인스턴스를 참조했을 경우이다.

 

다음 엔터티에는 null이 아닌 이름 속성이 있을 경우이다.

@Entity
public class Foo {
   ...

   @Column(nullable = false)
   private String name;

   ...
}

 

 

다음 테스트에서 name 에 대해 null 값이 있는 엔터티를 유지하려고 시도하는 경우

 

@Test(expected = DataIntegrityViolationException.class)
public void whenInvalidEntityIsCreated_thenDataException() {
   fooService.create(new Foo());
}

데이터베이스 통합 제약 조건이 위반되어 DataIntegrityViolationException 이 발생한다.

org.springframework.dao.DataIntegrityViolationException: 
not-null property references a null or transient value: 
org.baeldung.spring.persistence.model.Foo.name; 
nested exception is org.hibernate.PropertyValueException: 
not-null property references a null or transient value: 
org.baeldung.spring.persistence.model.Foo.name
	at o.s.orm.h.SessionFactoryUtils.convertHibernateAccessException(SessionFactoryUtils.java:160)
...
Caused by: org.hibernate.PropertyValueException: 
not-null property references a null or transient value: 
org.baeldung.spring.persistence.model.Foo.name
	at o.h.e.i.Nullability.checkNullability(Nullability.java:103)
...

 

 

 

Cause: org.hibernate.exception.DataException

Hibernate DataException은 유효하지 않은 SQL문임을 나타낸다.

특정 컨텍스트에서 문이나 데이터에 문제가 있을 때 이다. 

 

예를 들어 이전의 또는 Foo 엔터티를 사용하는 경우 다음은 이 예외를 트리거한다.

@Test(expected = DataIntegrityViolationException.class)
public final void whenEntityWithLongNameIsCreated_thenDataException() {
   service.create(new Foo(randomAlphabetic(2048)));
}

긴 이름 값을 가진 개체를 유지하자 예외가 발생한다

org.springframework.dao.DataIntegrityViolationException: 
could not execute statement; SQL [n/a]; 
nested exception is org.hibernate.exception.DataException: could not execute statement
   at o.s.o.h.SessionFactoryUtils.convertHibernateAccessException(SessionFactoryUtils.java:143)
...
Caused by: org.hibernate.exception.DataException: could not execute statement
	at o.h.e.i.SQLExceptionTypeDelegate.convert(SQLExceptionTypeDelegate.java:71)

이 특정 예에서 솔루션은 이름의 최대 길이를 지정해주면 된다.

@Column(nullable = false, length = 4096)

 

 

Cause: javax.persistence.EntityExistsException

JPA 예외가 데이터 무결성 위반의 유일한 잠재적 원인일 경우이다.

Simillarly to Hibernate, the EntityExistsException JPA exception will also be wrapped by the Spring Exception Translation into a DataIntegrityViolationException. The only difference is that JPA itself is already high level which makes this JPA exception the only potential cause of data integrity violations.

 

 

 

Potentially DataIntegrityViolationException

DataIntegrityViolationException이 예상되는 경우에 다른 예외가 발생할 수 있다.

이러한 경우 중 하나는 hibernate-validator 4 또는 5와 같은 JSR-303 유효성 검사기가 클래스 경로에 존재하는 경우이다.

 

이 경우 다음 엔터티가 name 에 대한 null 값으로 지속되는 경우 지속성 계층에 의해 트리거된 데이터 무결성 위반으로 인해 더 이상 실패하지 않는다 .

@Entity
public class Foo {
    ...
    @Column(nullable = false)
    @NotNull
    private String name;

    ...
}

 

이는 실행이 지속성 계층에 도달하지 않기 때문이다.

그 전에는 javax.validation.ConstraintViolationException 이 발생하여 실패한다

javax.validation.ConstraintViolationException: 
Validation failed for classes [org.baeldung.spring.persistence.model.Foo] 
during persist time for groups [javax.validation.groups.Default, ]
List of constraint violations:[ ConstraintViolationImpl{
    interpolatedMessage='may not be null', propertyPath=name, 
    rootBeanClass=class org.baeldung.spring.persistence.model.Foo, 
    messageTemplate='{javax.validation.constraints.NotNull.message}'}
]
    at o.h.c.b.BeanValidationEventListener.validate(BeanValidationEventListener.java:159)
    at o.h.c.b.BeanValidationEventListener.onPreInsert(BeanValidationEventListener.java:94)
728x90