同一事务内saveAndFlush持久化的实体无法被后续查询到,触发DataIntegrityViolationException异常的问题咨询
我来帮你分析下这个问题哈!你遇到的核心问题是:循环里明明已经用saveAndFlush把新的ProductB2C持久化到数据库了,但后续循环调用findByExtId却查不到这个实体,导致重复创建触发了唯一约束异常。
问题根源
这背后主要有两个原因:
- Hibernate缓存的特性:Hibernate的一级缓存(Session缓存)是按实体的主键ID来索引的,而你用
findByExtId是按extId这个非主键字段查询。默认情况下,JPQL查询不会主动从Session缓存里按非主键字段匹配实体,而是直接查数据库。虽然saveAndFlush会把实体同步到数据库,但在同一个未提交的事务里,偶尔会因为Session的快照机制或查询策略,导致查询无法立即获取到刚插入的记录(理论上同事务内应该可见,但实际场景中确实会出现这种情况)。 - 重复创建的逻辑漏洞:如果你的
productPrices列表里有多个条目对应同一个extId,循环里每次都执行“查询→不存在则创建”的逻辑,就会重复插入同一extId的ProductB2C,触发数据库的唯一约束。
解决方案
这里给你两个靠谱的解决思路,都能彻底解决问题:
方案1:用本地内存缓存避免重复操作
在循环外维护一个Map来缓存已经存在或刚创建的ProductB2C,这样同一个extId只会被创建一次,完全绕开查询不到的问题:
@Transactional(propagation = Propagation.REQUIRES_NEW) public void myFunction(List<ProductPriceCatalogResponseAPI.ProductPrice> productPrices) { // 本地缓存:用extId作为key,存对应的ProductB2C Map<Long, ProductB2C> productCache = new HashMap<>(); for (ProductPriceCatalogResponseAPI.ProductPrice productPrice : productPrices) { Long extId = productPrice.getProduct().getId(); // 先查缓存,没有再查数据库,还没有就创建并加入缓存 ProductB2C product = productCache.computeIfAbsent(extId, id -> productB2CRepository.findByExtId(id) .orElseGet(() -> createNewProductFromExtId(productPrice.getProduct())) ); ProductPriceB2C pp = new ProductPriceB2C(); pp.setProduct(product); productPriceB2CRepository.saveAndFlush(pp); } } private ProductB2C createNewProductFromExtId(ProductPriceCatalogResponseAPI.Product product) { ProductB2C p = new ProductB2C(); p.setName(product.getLabel()); p.setExtId(product.getId()); log.info("Persist product {} with external id {}", product.getLabel(), product.getId()); return productB2CRepository.saveAndFlush(p); }
这个方案不仅解决了重复创建的问题,还减少了数据库查询次数,性能也更好。
方案2:批量查询已存在的实体
先一次性把所有需要的extId提取出来,批量查询已存在的ProductB2C,再在循环里处理,避免循环内多次查询数据库:
@Transactional(propagation = Propagation.REQUIRES_NEW) public void myFunction(List<ProductPriceCatalogResponseAPI.ProductPrice> productPrices) { // 先提取所有不重复的extId Set<Long> extIds = productPrices.stream() .map(p -> p.getProduct().getId()) .collect(Collectors.toSet()); // 批量查询已存在的ProductB2C,转成以extId为key的Map Map<Long, ProductB2C> existingProducts = productB2CRepository.findByExtIdIn(extIds).stream() .collect(Collectors.toMap(ProductB2C::getExtId, p -> p)); for (ProductPriceCatalogResponseAPI.ProductPrice productPrice : productPrices) { Long extId = productPrice.getProduct().getId(); ProductB2C product = existingProducts.get(extId); // 如果不存在就创建,并存入Map if (product == null) { product = createNewProductFromExtId(productPrice.getProduct()); existingProducts.put(extId, product); } ProductPriceB2C pp = new ProductPriceB2C(); pp.setProduct(product); productPriceB2CRepository.saveAndFlush(pp); } }
注意:要在你的ProductB2CRepository里添加这个批量查询方法:
List<ProductB2C> findByExtIdIn(Set<Long> extIds);
额外优化建议
- 在
ProductB2C实体的extId字段上加上@Column(unique = true),让实体层和数据库的约束保持一致,能提前在代码层面做校验:
@Column(name = "ext_id", unique = true) private Long extId;
- 如果业务允许,也可以考虑开启
findByExtId的查询缓存,但要注意缓存的一致性问题,别随便用哦。
内容的提问来源于stack exchange,提问作者stephane brun
相关产品推荐
相关产品推荐

