Hibernate一对多双向更新时出现同一实体多表示错误的原因及解决方法
异常原因
该异常是JPA持久化上下文检测到同一数据库实体对应了两个不同的Java实例导致的,核心诱因有两个:
PersonCommunication类的@ManyToOne注解错误配置了cascade=CascadeType.ALL:你通过modelMapper映射生成的incoming对象里,communications集合中的每个PersonCommunication实例都持有一个游离状态的Person对象,该对象ID和你从数据库查询到的托管状态StaffMember(StaffMember是Person的子类)ID完全一致。执行save操作级联合并时,JPA发现同一个实体ID对应了「托管状态的StaffMember实例」和「游离状态的Person实例」两个不同对象,直接抛出冲突。- 直接替换关联集合时没有维护双向关联关系:你直接把新的
communications集合赋值给托管的StaffMember实例,新集合里的PersonCommunication并没有绑定当前托管的StaffMember对象,仍然持有映射生成的游离Person引用,进一步加剧了实例冲突。
修复方案
第一步:移除错误的级联配置
删掉PersonCommunication类中@ManyToOne注解的级联配置,子表不需要配置反向级联操作父表,父子关系的级联由父表Person的@OneToMany配置维护即可:
// 修改前 @ManyToOne(cascade = CascadeType.ALL) private Person person; // 修改后 @ManyToOne private Person person;
第二步:更新集合时维护双向关联
不要直接替换整个communications集合,先清理原有关联,再给新的通信记录绑定当前托管的StaffMember实例,修改后的Service层代码如下:
Optional<StaffMember> staffMemberOptional = staffRepository.findById(id); StaffMember incoming = modelMapper.map(staffMemberDTO, StaffMember.class); if(staffMemberOptional.isPresent()){ StaffMember existing = staffMemberOptional.get(); existing.setFirstName(incoming.getFirstName()); // 解除原有通信记录的关联 existing.getCommunications().forEach(comm -> comm.setPerson(null)); existing.getCommunications().clear(); // 给新的通信记录绑定当前托管的人员实例 if(incoming.getCommunications() != null){ incoming.getCommunications().forEach(comm -> comm.setPerson(existing)); existing.getCommunications().addAll(incoming.getCommunications()); } // 处于持久化上下文的托管对象事务提交时会自动脏更新,也可以显式调用save }else{ // 新增场景也要维护关联关系 if(incoming.getCommunications() != null){ incoming.getCommunications().forEach(comm -> comm.setPerson(incoming)); } staffRepository.save(incoming); }
内容的提问来源于stack exchange,提问作者D.Anush
相关产品推荐
相关产品推荐

