单元测试中Spring DataIntegrityViolationException异常问题排查
批量执行测试时Spring DataIntegrityViolationException外键约束错误(单独测试正常)
当运行SaleReceiptRepositoryTest整个测试类时,第二个测试方法givenReceiptNumber_whenFind_thenReturnEntity总是抛出DataIntegrityViolationException,提示ProductSales表的外键SALE_RECEIPT_NUMBER引用SaleReceipts表ID违反参照完整性约束,但单独运行每个测试方法均可通过。
问题根源分析
- 双向关联未维护:
ProductSale实体未关联回对应的SaleReceipt,导致Hibernate保存ProductSale时,外键字段未被正确填充为当前SaleReceipt的ID,而是使用默认值(如1)。第一个测试事务回滚后,ID为1的SaleReceipt不存在,第二个测试保存时触发外键约束。 - ID序列未重置:事务回滚不会重置Hibernate的ID生成序列,后续测试的
SaleReceiptID会从之前的序列值继续递增,若ProductSale的外键未正确关联,会匹配不到实际存在的ID。 - 批量删除不彻底:
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
相关产品推荐
相关产品推荐

