Перейти к основному содержимому

Spring DataIntegrityViolationException

· 5 мин. чтения

1. Обзор

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

2. DataIntegrityViolationException и Spring Exception Translation

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

<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 с Hibernate

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

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

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

3.2. DataIntegrityViolationException с JPA

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

Существует единственное исключение JPA, которое может вызвать возникновение исключения DataIntegrityViolationExceptionjavax.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 это сводится к тому, что сущность сохраняется с проблемой. Либо у объекта есть свойство null, которое определяется с помощью ограничения not-null , либо ассоциация объекта может ссылаться на несохраненный временный экземпляр .

Например, следующий объект имеет ненулевое свойство имени :

@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.foreach.spring.persistence.model.Foo.name;
nested exception is org.hibernate.PropertyValueException:
not-null property references a null or transient value:
org.foreach.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.foreach.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.

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

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

...
}

Это связано с тем, что выполнение не попадет на уровень персистентности — до этого оно завершится с ошибкой javax.validation.ConstraintViolationException :

javax.validation.ConstraintViolationException: 
Validation failed for classes [org.foreach.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.foreach.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, поэтому его легко импортировать и запускать как есть.