多事务删存大实体触发重复主键异常但实体保存正常的排查
问题原因分析
核心问题出在JPQL删除操作绕过了Hibernate的一级缓存,导致持久化上下文里的旧数据没有同步更新:
- 用
DELETE FROM Scheme s WHERE s.projectInfo.id = :projectId这类JPQL语句删除旧Scheme时,数据库记录确实被删除,但当前线程的持久化上下文(比如Facade方法所在的事务上下文)中,ProjectInfo对象仍关联着旧的Scheme实例。 - 后续给
ProjectInfo设置新Scheme并保存后,旧Scheme实例还留在缓存里。当Hibernate刷新持久化上下文时,会尝试将这个旧实例再次插入数据库,触发project_id的唯一约束冲突。 - 而新
Scheme是在独立的REQUIRES_NEW事务里保存的,已成功提交到数据库,所以最终所有实体都保存成功,但过程中抛出了约束异常。
可行解决方案
方案1:替换JPQL删除为实体查询后删除
不用JPQL直接删除,先查出旧Scheme实例再执行删除,Hibernate会自动更新持久化上下文,避免旧实例残留:
// SchemeRepository删除方法修改为 public boolean delete(String projectId) { Scheme oldScheme = entityManager.createQuery("SELECT s FROM Scheme s WHERE s.projectInfo.id = :projectId", Scheme.class) .setParameter("projectId", projectId) .getSingleResult(); if (oldScheme != null) { entityManager.remove(oldScheme); return true; } return false; }
方案2:删除后刷新ProjectInfo实例
在delete操作完成后,强制刷新ProjectInfo的状态,让持久化上下文同步数据库真实状态,清除旧Scheme引用:
// Facade层方法修改 private void updateProjectScheme(Scheme receivedScheme, ProjectInfo projectInfo) { if (projectInfo.getScheme() != null) { this.schemeService.delete(projectInfo.getId()); // 刷新ProjectInfo,清除缓存里的旧Scheme引用 entityManager.refresh(projectInfo); receivedScheme.setProjectInfo(projectInfo); projectInfo.setScheme(receivedScheme); this.schemeService.save(receivedScheme); } else { receivedScheme.setProjectInfo(projectInfo); projectInfo.setScheme(receivedScheme); this.schemeService.save(receivedScheme); } }
注:需在Facade层注入EntityManager执行refresh操作
方案3:清空持久化上下文
在delete操作后,调用entityManager.clear()清空当前持久化上下文,彻底清除所有缓存实体:
private void updateProjectScheme(Scheme receivedScheme, ProjectInfo projectInfo) { if (projectInfo.getScheme() != null) { this.schemeService.delete(projectInfo.getId()); // 清空缓存,避免旧实例干扰 entityManager.clear(); // 重新查询ProjectInfo,因为clear后原实例已脱离上下文 ProjectInfo freshProject = projectInfoRepository.findById(projectInfo.getId()).orElseThrow(); receivedScheme.setProjectInfo(freshProject); freshProject.setScheme(receivedScheme); this.schemeService.save(receivedScheme); } else { receivedScheme.setProjectInfo(projectInfo); projectInfo.setScheme(receivedScheme); this.schemeService.save(receivedScheme); } }
内容的提问来源于stack exchange,提问作者Ярослав Маринов
相关产品推荐
相关产品推荐

