为啥 Hibernate 在一对多双向更新操作中给出同一实体的多个表示?

Posted

技术标签:

【中文标题】为啥 Hibernate 在一对多双向更新操作中给出同一实体的多个表示?【英文标题】:Why Hibernate giving Multiple representations of the same entity in One to Many Bidirectional update actions?为什么 Hibernate 在一对多双向更新操作中给出同一实体的多个表示? 【发布时间】:2021-12-23 01:16:47 【问题描述】:

我尝试了很多解决方案,但都失败了。试图实现的是更新操作。 我有两个实体,例如人和人通信。

@Entity
@Table(name = "person")
@SequenceGenerator(name = "person_id_seq", sequenceName = "person_id_seq", allocationSize = 1)
@Inheritance(strategy = InheritanceType.JOINED)
@Getter
@Setter
public class Person 
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "person_id_seq")
    protected int id;

    @OrderBy(value = "communicationAddress ASC")
    @OneToMany(targetEntity = PersonCommunication.class,cascade=CascadeType.ALL, mappedBy = "person")
    protected List<PersonCommunication> communications;


@Getter
@Setter
@Entity
@Table(name = "person_communication")
@SequenceGenerator(name = "person_communication_id_seq", sequenceName = "person_communication_id_seq", allocationSize = 1)
public class PersonCommunication 
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "person_communication_id_seq")
    private int id;

    @ManyToOne(cascade = CascadeType.ALL)
    private Person person;

当我尝试添加和更新时,在我的服务类中,假设我想添加一些通信,通过 id 找到一个人,如果存在则更新通信而不是像这样保存为新人。

Optional<StaffMember> staffMemberOptional = staffRepository.findById(id);
StaffMember incoming = modelMapper.map(staffMemberDTO, StaffMember.class);
if(staffMemberOptional.isPresent())
  staffMemberOptional.get().setCommunications(incoming.getCommunications());
  staffMemberOptional.get().setFirstName(incoming.getFirstName());
  staffRepository.save(staffMemberOptional.get());
else
  staffRepository.save(incoming);

当我尝试更新时,它会出现异常。

***"Multiple representations of the same entity [com.impactivo.smartpcmh.server.model.StaffMember#2050] are being merged. Detached: [com.impactivo.smartpcmh.server.model.Person@1ad3d552]; Managed: [com.impactivo.smartpcmh.server.model.StaffMember@12f382f3]; nested exception is java.lang.IllegalStateException: Multiple representations of the same entity [com.impactivo.smartpcmh.server.model.StaffMember#2050] are being merged. Detached: [com.impactivo.smartpcmh.server.model.Person@1ad3d552]; Managed: [com.impactivo.smartpcmh.server.model.StaffMember@12f382f3]",***

为什么会这样以及如何解决这个问题?

谢谢。

【问题讨论】:

1.实体没有正确的hashCodeequals 实现,这些很重要。 2.确保此代码在事务方法中,以便所有内容都在单个事务中(我怀疑它不是事务性的,导致多个事务和分离的实体)。 3. 请正确使用Optional 你所做的或多或少是一种反模式。 如果是这样,任何可能的推荐解决方案? 使方法具有事务性并实现正确的哈希码和等于方法。 【参考方案1】:

发生这种情况是因为您在收集了同一实体的托管版本之后尝试合并分离的实体。

在事务中获取实体时,您会返回一个托管实体,并且在同一个事务中再次获取它会返回相同的对象。另一方面,如果您越过事务边界,实体将变得分离,如果您开始一个新事务并再次获取相同的实体,您实际上会得到一个不同的对象。然后,如果您尝试重新引入分离的实体(使用合并),Hibernate 会抱怨,因为它会有两个对象用于同一个实体,并且它无法知道其中哪个包含正确的状态以写入数据库。

在我看来,整个分离-合并模式是一种反模式,应该避免。您应该在事务中获取实体、进行更改并提交事务(应将其配置为刷新所有更改,无需运行合并)。

// (1)this will load a managed version of the entity
Optional<StaffMember> staffMemberOptional = staffRepository.findById(id); 
// (2) this will create a detached version of the entity
StaffMember incoming = modelMapper.map(staffMemberDTO, StaffMember.class); 
if(staffMemberOptional.isPresent())
  staffMemberOptional.get().setCommunications(incoming.getCommunications());
  staffMemberOptional.get().setFirstName(incoming.getFirstName());
  staffRepository.save(staffMemberOptional.get());
else
  //this will try to reintroduce the detached entity, but since it was already
  //loaded in (1), the entity manager will end up with 2 versions of the same entity 
  staffRepository.save(incoming);
 

【讨论】:

嗨@Tobb,感谢您的回复。我尝试映射您所说的内容,但似乎我无法理解。如果可能的话,你能给我看一下代码片段吗? 添加你的代码和一些 cmets 来澄清。 但是,是的,这不应该发生,因为您检查了是否存在可选选项。 代码中可能存在您尚未显示的地方,其中重新引入了来自 DTO 的人员实体(以人员对象的形式) 那么有什么可能的解决方案或推荐的方法来解决这个问题吗?

以上是关于为啥 Hibernate 在一对多双向更新操作中给出同一实体的多个表示?的主要内容,如果未能解决你的问题,请参考以下文章

三大框架 之 Hibernate查询(一对多多对多查询关系)

hibernate 一对多双向的问题~~~

hibernate中配置单向多对一关联,和双向一对多

Hibernate双向一对多映射关系

Hibernate JPA双向一对多结果与约束冲突异常

郁闷了,非常简单的hibernate更新操作为啥就不执行