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