Spring DataIntegrityViolationException

2023/05/13

1. 概述

在本文中,我们将讨论Spring org.springframework.dao.DataIntegrityViolationException–这是一个通用数据异常,通常由Spring异常转换机制在处理较低级别的持久性异常时抛出。本文将讨论此异常的最常见原因以及每个原因的解决方案。

延伸阅读

Spring Data Java 8支持

Spring Data中Java 8支持的快速实用指南。

阅读更多

Spring Data注解

了解我们使用Spring Data项目处理持久性所需的最重要的注解

阅读更多

在Spring Data REST中处理关系

在Spring Data REST中处理实体关系的实用指南。

阅读更多

2. DataIntegrityViolationException与Spring异常翻译

Spring异常转换机制可以透明地应用于所有用@Repository注解的bean-通过在Context中定义一个异常转换bean后处理器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 Hibernate的DataIntegrityViolationException

Spring配置Hibernate时,在Spring提供的异常转换层-SessionFactoryUtils-convertHibernateAccessException中抛出异常。

存在三种可能导致抛出DataIntegrityViolationException的Hibernate异常:

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

3.2 JPA的DataIntegrityViolationException

当Spring配置为使用JPA作为其持久性提供程序时,类似于Hibernate,在异常转换层中抛出DataIntegrityViolationException-即在EntityManagerFactoryUtils-convertJpaAccessExceptionIfPossible中。

有一个JPA异常可能触发抛出DataIntegrityViolationException–javax.persistence.EntityExistsException。

4. 原因:org.hibernate.exception.ConstraintViolationException

这是迄今为止引发DataIntegrityViolationException的最常见原因——HibernateConstraintViolationException表示该操作违反了数据库完整性约束。

考虑以下示例——对于通过父实体和子实体之间的显式外键列的一对一映射——以下操作应该失败:

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

Parent实体有一个指向Child实体的外键,因此删除子实体会破坏Parent上的外键约束这会导致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

要解决此问题,应先删除Parent:

@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中,这将归结为实体被持久化时出现问题。要么实体有一个用非空约束定义的空属性,要么实体的关联可能引用一个未保存的瞬态实例。

例如,以下实体具有非空名称属性

@Entity
public class Foo {
    // ...

    @Column(nullable = false)
    private String name;

    // ...
}

如果以下测试尝试使用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

HibernateDataException表示无效的SQL语句-在该特定上下文中语句或数据有问题。例如,使用之前的orFoo实体,以下将触发此异常:

@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类似,EntityExistsExceptionJPA异常也将由SpringExceptionTranslation包装成DataIntegrityViolationException。唯一的区别是JPA本身已经是高级别的,这使得这个JPA异常成为数据完整性违规的唯一潜在原因。

8. 潜在的DataIntegrityViolationException

在某些可能预期会出现DataIntegrityViolationException的情况下,可能会抛出另一个异常-一种情况是类路径上存在JSR-303验证程序,例如hibernate-validator4或5。

在这种情况下,如果以下实体以name的空值持久化,它将不再因持久层触发的数据完整性违规而失败:

@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. 总结

在本文的最后,我们应该有一个清晰的地图来导航可能导致Spring中的DataIntegrityViolationException的各种原因和问题,以及如何解决所有这些问题。

与往常一样,本教程的完整源代码可在GitHub上获得。

Show Disqus Comments

Post Directory

扫码关注公众号:Taketoday
发送 290992
即可立即永久解锁本站全部文章