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

多事务删存大实体触发重复主键异常但实体保存正常的排查

问题原因分析

核心问题出在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,提问作者Ярослав Маринов

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.04 11:35:54