«1. Обзор

В этой статье мы обсудим Spring org.springframework.dao.DataIntegrityViolationException — это универсальное исключение данных, которое обычно вызывается механизмом преобразования исключений Spring при работе с исключениями сохранения более низкого уровня. В статье будут обсуждаться наиболее распространенные причины этого исключения, а также решения для каждой из них.

2. DataIntegrityViolationException и трансляция исключений Spring

Механизм трансляции исключений Spring может быть прозрачно применен ко всем bean-компонентам, аннотированным @Repository, путем определения bean-компонента post-processor bean-компонента трансляции исключений в контексте:

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

~~ ~ Или в Java:

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

Механизм трансляции исключений также включен по умолчанию в более старых шаблонах персистентности, доступных в Spring — HibernateTemplate, JpaTemplate и т. д.

3. Куда выбрасывается исключение DataIntegrityViolationException

3.1. DataIntegrityViolationException with Hibernate

Когда Spring настроен с Hibernate, исключение создается на уровне перевода исключений, предоставляемом Spring — SessionFactoryUtils — convertHibernateAccessException.

Существует три возможных исключения Hibernate, которые могут вызвать выброс DataIntegrityViolationException:

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

3.2. DataIntegrityViolationException With JPA

поставщика постоянства DataIntegrityViolationException, аналогично Hibernate, выбрасывается на уровне преобразования исключений, а именно в EntityManagerFactoryUtils, convertJpaAccessExceptionIfPossible.

Существует единственное исключение JPA, которое может вызвать выброс DataIntegrityViolationException — javax.persistence.EntityExistsException.

4. Причина: 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);
}

Родительский объект имеет внешний ключ для дочерняя сущность — поэтому удаление дочерней сущности нарушит ограничение внешнего ключа для родителя — что приводит к ConstraintViolationException — обернутому Spring в DataIntegrityViolationException:

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);
}

5. Причина: org.hibernate.PropertyValueException

Это одна из наиболее распространенных причин DataIntegrityViolationException — в Hibernate это сводится к тому, что объект сохраняется с проблема. Либо сущность имеет нулевое свойство, определенное с ненулевым ограничением, либо ассоциация сущности может ссылаться на несохраненный временный экземпляр.

Например, следующая сущность имеет ненулевое свойство name –

@Entity
public class Foo {
   ...

   @Column(nullable = false)
   private String name;

   ...
}

Если следующий тест попытается сохранить сущность с нулевым значением имени:

@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)
...

6. Причина: 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)

7. Причина: javax.persistence.EntityExistsException

Аналогично Hibernate, исключение JPA EntityExistsException также будет преобразовано Spring Exception Translation в DataIntegrityViolationException. Единственное отличие состоит в том, что сам JPA уже имеет высокий уровень, что делает это исключение JPA единственной потенциальной причиной нарушения целостности данных.

«8. Потенциально DataIntegrityViolationException

В некоторых случаях, когда можно ожидать DataIntegrityViolationException, может быть выдано другое исключение — например, если в пути к классам существует валидатор JSR-303, такой как hibernate-validator 4 или 5.

В этом случае, если следующий объект сохраняется с нулевым значением для имени, он больше не будет давать сбой из-за нарушения целостности данных, вызванного уровнем сохранения:

@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)

9. Выводы

В конце этой статьи у нас должна быть четкая карта для навигации по разнообразию причин и проблем, которые могут привести к DataIntegrityViolationException в Spring, а также хорошее понимание того, как исправить все эти проблемы.

Реализацию всех примеров исключений можно найти в проекте github — это проект на основе Eclipse, поэтому его легко импортировать и запускать как есть.