«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, поэтому его легко импортировать и запускать как есть.