You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

单元测试中Spring DataIntegrityViolationException异常问题排查

批量执行测试时Spring DataIntegrityViolationException外键约束错误(单独测试正常)

当运行SaleReceiptRepositoryTest整个测试类时,第二个测试方法givenReceiptNumber_whenFind_thenReturnEntity总是抛出DataIntegrityViolationException,提示ProductSales表的外键SALE_RECEIPT_NUMBER引用SaleReceipts表ID违反参照完整性约束,但单独运行每个测试方法均可通过。

问题根源分析

  1. 双向关联未维护:ProductSale实体未关联回对应的SaleReceipt,导致Hibernate保存ProductSale时,外键字段未被正确填充为当前SaleReceipt的ID,而是使用默认值(如1)。第一个测试事务回滚后,ID为1的SaleReceipt不存在,第二个测试保存时触发外键约束。
  2. ID序列未重置:事务回滚不会重置Hibernate的ID生成序列,后续测试的SaleReceiptID会从之前的序列值继续递增,若ProductSale的外键未正确关联,会匹配不到实际存在的ID。
  3. 批量删除不彻底:deleteAll()是批量操作,不触发实体级联删除逻辑,可能残留关联数据导致外键引用异常。

解决方案

1. 维护双向关联关系

确保SaleReceipt和ProductSale的双向关联被正确维护,在添加ProductSale到SaleReceipt时,同时设置ProductSale的关联对象:

private SaleReceipt saveReferencesAndSet() {
    SaleReceipt receipt = SaleReceiptCreator.entity();
    Customer customer = customerRepository.save(receipt.getCustomer());
    receipt.setCustomer(customer);
    List<ProductSale> productSales = ProductSaleCreator.entityList();

    for(ProductSale productSale: productSales) {
        arrangeProduct(productSale);
        // 维护双向关联,设置ProductSale对应的SaleReceipt
        productSale.setSaleReceipt(receipt);
    }

    receipt.setProductSaleList(productSales);

    return receipt;
}

同时检查实体类的映射配置:

  • SaleReceipt类:
@OneToMany(mappedBy = "saleReceipt", cascade = CascadeType.ALL, orphanRemoval = true)
private List<ProductSale> productSaleList;
  • ProductSale类:
@ManyToOne
@JoinColumn(name = "sale_receipt_number")
private SaleReceipt saleReceipt;

2. 重置数据库ID序列

在@BeforeEach中添加重置ID序列的逻辑,确保每次测试的ID从1开始(以H2数据库为例):

@Autowired
private EntityManager entityManager;

@BeforeEach
public void beforeEach(){
    // 按从子表到父表的顺序删除数据,避免外键约束
    entityManager.createNativeQuery("TRUNCATE TABLE product_sales RESTART IDENTITY CASCADE").executeUpdate();
    entityManager.createNativeQuery("TRUNCATE TABLE sale_receipts RESTART IDENTITY CASCADE").executeUpdate();
    entityManager.createNativeQuery("TRUNCATE TABLE products RESTART IDENTITY CASCADE").executeUpdate();
    entityManager.createNativeQuery("TRUNCATE TABLE product_barcodes RESTART IDENTITY CASCADE").executeUpdate();
    entityManager.createNativeQuery("TRUNCATE TABLE customers RESTART IDENTITY CASCADE").executeUpdate();
}

TRUNCATE TABLE ... RESTART IDENTITY会删除表数据并重置自增ID,CASCADE会级联删除关联的子表数据,彻底清除测试残留。

3. 调整事务传播策略

将测试方法的事务传播行为改为REQUIRES_NEW,确保每个测试方法在独立的事务中运行,避免事务间的状态影响:

import org.springframework.transaction.annotation.Propagation;

@Test
@DisplayName("Add receipt")
@Order(1)
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void givenEntityReceipt_thenAdd_thenReturnReceiptEntity(){
    // 测试代码不变
}

@Test
@DisplayName("Get receipt by number")
@Order(2)
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void givenReceiptNumber_whenFind_thenReturnEntity(){
    // 测试代码不变
}

4. 检查实体生成器逻辑

确保SaleReceiptCreator和ProductSaleCreator生成的实体没有硬编码ID或外键值,所有关联关系都通过代码动态设置,避免固定值导致的匹配错误。


内容的提问来源于stack exchange,提问作者Murad

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.30 14:00:54