1. Общ преглед
В тази статия ще обсъдим Spring org.springframework.dao.DataIntegrityViolationException - това е родово изключение на данните, обикновено изхвърлено от механизма за превод на изключения Spring, когато се занимаваме с изключения за устойчивост на по-ниско ниво. Статията ще обсъди най-честите причини за това изключение заедно с решението за всяка една.
2. Превод на DataIntegrityViolationException и Spring Exception
Механизмът за превод на Spring Spring може да се приложи прозрачно към всички зърна, коментирани с @Repository - чрез дефиниране на зърно за процесор на преобразуване на изключение в контекста:
Или в Java:
@Configuration public class PersistenceHibernateConfig{ @Bean public PersistenceExceptionTranslationPostProcessor exceptionTranslation(){ return new PersistenceExceptionTranslationPostProcessor(); } }
Механизмът за превод на изключения също е активиран по подразбиране за по-стария шаблон за постоянство, наличен през пролетта - HibernateTemplate, JpaTemplate и др
3. Къде е хвърлен DataIntegrityViolationException
3.1. DataIntegrityViolationException с хибернация
Когато Spring е конфигуриран с Hibernate, изключението се хвърля в слоя за превод на изключения, предоставен от Spring - SessionFactoryUtils - convertHibernateAccessException .
Има три възможни изключения за хибернация, които могат да доведат до изхвърляне на DataIntegrityViolationException :
- org.hibernate.exception.ConstraintViolationException
- org.hibernate.PropertyValueException
- org.hibernate.exception.DataException
3.2. DataIntegrityViolationException с JPA
Когато Spring е конфигуриран с JPA като доставчик на постоянство, DataIntegrityViolationException се хвърля, подобно на Hibernate, в слоя за превод на изключения - а именно в EntityManagerFactoryUtils - convertJpaAccessExceptionIfPossible .
Има един-единствен на СПА изключение, което може да предизвика 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); }
Обектът родител има външен ключ към обекта Child - така че изтриването на детето би нарушило ограничението за външен ключ на родителя - което води до 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 - в режим на хибернация това ще стигне до обект, който продължава да съществува с проблем. Или обектът има нулево свойство, което е дефинирано с ненулево ограничение , или асоциация на обекта може да препраща към незаписан, преходен екземпляр .
Например, следният обект има свойство не-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.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
Хибернация на 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
Подобно на хибернация, изключението 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 през пролетта, както и добро разбиране за това как да отстраним всички тези проблеми.
Прилагането на всички примери за изключения може да бъде намерено в проекта github - това е проект, базиран на Eclipse, така че трябва да е лесно да се импортира и да се изпълнява както е.