2. 1. 异常概述

DataIntegrityViolationException 是 Spring 框架中一个通用的数据异常,通常由 Spring 异常转换机制在处理底层持久化异常时抛出。本文将深入分析该异常的常见触发原因及对应的解决方案。

3. 2. 异常转换机制与 Spring

Spring 的异常转换机制可透明应用于所有标注 @Repository 的 Bean。通过以下两种方式配置:

XML 配置方式

<bean id="persistenceExceptionTranslationPostProcessor" 
   class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor" />

Java 配置方式

@Configuration
public class PersistenceHibernateConfig{
   @Bean
   public PersistenceExceptionTranslationPostProcessor exceptionTranslation(){
      return new PersistenceExceptionTranslationPostProcessor();
   }
}

⚠️ 该机制在 Spring 旧版持久化模板(如 HibernateTemplateJpaTemplate)中默认启用。

4. 3. 异常抛出位置

3.1 Hibernate 环境下的抛出点

当 Spring 与 Hibernate 集成时,异常在 SessionFactoryUtils.convertHibernateAccessException 层抛出。以下三种 Hibernate 异常会触发 DataIntegrityViolationException

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

3.2 JPA 环境下的抛出点

当 Spring 使用 JPA 作为持久化提供者时,异常在 EntityManagerFactoryUtils.convertJpaAccessExceptionIfPossible 层抛出。唯一可能触发该异常的 JPA 异常是:

  • javax.persistence.EntityExistsException

5. 4. 原因:数据库约束违反

这是最常见的原因!当操作违反数据库完整性约束时,Hibernate 抛出 ConstraintViolationException,被 Spring 包装为 DataIntegrityViolationException

典型场景

考虑 Parent 和 Child 实体通过外键建立的一对一关系:

@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的外键
}

执行结果:

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); // ✅ 先删除Parent
   childService.delete(childEntity); // 再安全删除Child
}

6. 5. 原因:属性值异常

当持久化实体存在以下问题时触发:

  1. 标注 @Column(nullable = false) 的属性为 null
  2. 关联实体引用了未保存的临时实例

问题实体示例

@Entity
public class Foo {
   ...

   @Column(nullable = false)
   private String name; // 必填字段

   ...
}

测试用例

@Test(expected = DataIntegrityViolationException.class)
public void whenInvalidEntityIsCreated_thenDataException() {
   fooService.create(new Foo()); // ❌ 未设置name属性
}

异常堆栈:

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)
...

7. 6. 原因:数据类型异常

当 SQL 语句或数据本身存在问题时触发,例如字段长度超限。

问题场景

@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) // ✅ 设置合理长度

8. 7. 原因:JPA 实体已存在异常

在 JPA 环境下,EntityExistsException 会被 Spring 包装为 DataIntegrityViolationException。这是 JPA 层面唯一可能触发数据完整性异常的情况。

9. 8. 可能被拦截的异常场景

当类路径存在 JSR-303 验证器(如 hibernate-validator 4/5)时,某些数据完整性异常可能被提前拦截。

添加验证注解的实体

@Entity
public class Foo {
    ...
    @Column(nullable = false)
    @NotNull // ✅ 添加JSR-303验证
    private String name;

    ...
}

此时尝试保存 namenull 的实体,会抛出验证异常而非持久化异常:

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)

10. 9. 总结

通过本文分析,我们系统梳理了 DataIntegrityViolationException 的常见触发场景及解决方案:

异常类型 典型场景 解决方案
数据库约束违反 外键引用被删除 按正确顺序删除实体
属性值异常 必填字段为空 设置非空值或修改约束
数据类型异常 字段长度超限 调整字段长度限制
JPA 实体重复 重复插入唯一实体 检查实体唯一性

所有示例代码可在 GitHub 项目 中获取(基于 Eclipse,可直接导入运行)。


原始标题:Spring DataIntegrityViolationException | Baeldung