简体   繁体   English

单向@OneToMany 关联未通过 JPA 中的相等性测试

[英]Unidirectional @OneToMany association fails equality test in JPA

I have set up a unidirectional OneToMany relationship like the example in section 2.10.5.1 of the JPA 2.1 spec:我已经建立了一个单向的 OneToMany 关系,就像 JPA 2.1 规范的第 2.10.5.1 节中的例子:

@Entity
public class Client implements Serializable {

...

    @OneToMany
    private List<ServiceOrder> activeServiceOrders;

    public void setActiveServiceOrders( List<ServiceOrder> activeServiceOrders ) {

        this.activeServiceOrders = activeServiceOrders;
    }

    public List<ServiceOrder> getActiveServiceOrders() {

        return activeServiceOrders;
    }
}

The ServiceOrder class implements hashCode and equals using its auto-generated long id. ServiceOrder 类使用其自动生成的 long id 实现 hashCode 和 equals。 They were implemented by Eclipse.它们是由 Eclipse 实现的。

public class ServiceOrder implements Serializable {

    @TableGenerator( name = "generator_serviceOrder", table = "SEQUENCE_TABLE", pkColumnName = "SEQ_NAME", valueColumnName = "LAST_VALUE_GEN", pkColumnValue = "SERVICE_ORDER_SEQ", allocationSize = 1, initialValue = 0 )
    @Id
    @GeneratedValue( strategy = GenerationType.TABLE, generator = "generator_serviceOrder" )
    private long id;
...
    @Override
    public boolean equals( Object obj ) {

        if ( this == obj )
            return true;
        if ( obj == null )
            return false;
        if ( getClass() != obj.getClass() )
            return false;
        ServiceOrder other = (ServiceOrder ) obj;
        if ( id != other.id )
            return false;
        return true;
    }
...
}

Tables are all auto-generated as expected.表格都是按预期自动生成的。 Then, when I want to establish the relationship I do:然后,当我想建立关系时,我会这样做:

...
Client client = entityManager.find(...);
ServiceOrder so = entityManager.find(...);
client.getActiveServiceOrders().add( so );
...

Everything is fine until now, transaction commits successfully.到目前为止一切都很好,事务提交成功。 Problem starts when I try to remove the relationship (in another transaction, another moment):当我尝试删除关系(在另一个事务中,另一个时刻)时,问题就开始了:

...
Client sameClient = entityManager.find(...);
ServiceOrder sameSo = entityManager.find(...);
log.info(sameClient.getActiveServiceOrders().size()); // "1", OK
log.info(sameClient.getActiveServiceOrders().contains(so)); // "false". Why?
sameClient.getActiveServiceOrders().remove(so); // does nothing, returns false
...

I debugged and discovered that the following is failing in ServiceOrder.equals():我调试并发现以下在 ServiceOrder.equals() 中失败:

...
if ( getClass() != obj.getClass() ) // different probably because JPA (Hibernate) proxies one of the objects
    return false; // returns
...

I found two temporary solutions:我找到了两个临时解决方案:

  1. Remove ServiceOrder equals() and hashCode();移除 ServiceOrder equals() 和 hashCode(); or或者
  2. Make the relationship bidirectional (and of course update both sides every add/remove);使关系双向(当然,每次添加/删除时都要更新双方);

I don't understand this behavior.我不明白这种行为。 Why the difference in treatment if the relationship is uni or bi-directional?如果关系是单向或双向的,为什么在处理上有所不同? Also, if I get these entities in the context of the same transaction, how would fail the first equals test:此外,如果我在同一事务的上下文中获取这些实体,那么第一个 equals 测试将如何失败:

if ( this == obj )
    return true;

I'm using JPA 2.1 (Wildfly 8.1.0).我正在使用 JPA 2.1(Wildfly 8.1.0)。

Best Regards and thank you in advance.最好的问候,并提前感谢您。 Renan雷南

You should override equals and hashCode but you should never use the ID for hash code unless you make the hashCode immutable and use the ID only when it's not null for equality .你应该覆盖 equals 和 hashCode 但你不应该使用 ID 作为哈希码,除非你使 hashCode 不可变并且只有当它不为 null时才使用 ID 作为 equals 。

Otherwise, prior to saving an Entity with the ID being null which is to be assigned during the flush time when you add a Transient entity to a collection, the moment it gets persisted and the ID is generated the equals/hashCode contract is going to broken.否则,在保存 ID 为 null 的实体之前,当您将 Transient 实体添加到集合中时,将在刷新时间分配该实体,当它被持久化并生成 ID 时,equals/hashCode 合同将被破坏.

Hibernate best practices suggest using a business key for object equality/hashCode. Hibernate 最佳实践建议使用业务键来实现对象相等性/hashCode。

So quoting the reference documentation:所以引用参考文档:

The general contract is: if you want to store an object in a List, Map or a Set then it is a requirement that equals and hashCode are implemented so they obey the standard contract as specified in the documentation.一般约定是:如果您想将对象存储在 List、Map 或 Set 中,那么需要实现 equals 和 hashCode,以便它们遵守文档中指定的标准约定。

To avoid this problem we recommend using the "semi"-unique attributes of your persistent class to implement equals() (and hashCode()).为了避免这个问题,我们建议使用持久类的“半”唯一属性来实现 equals()(和 hashCode())。 Basically you should think of your database identifier as not having business meaning at all (remember, surrogate identifier attributes and automatically generated values are recommended anyway).基本上,您应该认为您的数据库标识符根本没有商业意义(请记住,无论如何建议使用代理标识符属性和自动生成的值)。 The database identifier property should only be an object identifier, and basically should be used by Hibernate only.数据库标识符属性应该只是一个对象标识符,并且基本上应该只被 Hibernate 使用。 Of course, you may also use the database identifier as a convenient read-only handle, eg to build links in web applications.当然,您也可以将数据库标识符用作方便的只读句柄,例如在 Web 应用程序中构建链接。

Instead of using the database identifier for the equality comparison, you should use a set of properties for equals() that identify your individual objects.不使用数据库标识符进行相等比较,您应该使用一组用于标识各个对象的 equals() 属性。 For example, if you have an "Item" class and it has a "name" String and "created" Date, I can use both to implement a good equals() method.例如,如果您有一个“Item”类并且它有一个“name”String 和“created”Date,我可以使用两者来实现一个好的 equals() 方法。 No need to use the persistent identifier, the so-called "business key" is much better.无需使用持久标识符,所谓的“业务密钥”要好得多。 It's a natural key, but this time there is nothing wrong with using it!是天生的钥匙,不过这次用起来也没什么错!

Don't override the equals and hashCode .不要覆盖equalshashCode Hibernate has its own implementation to find out the objects, and that's why you don't get the expected result. Hibernate 有自己的实现来找出对象,这就是为什么你没有得到预期的结果。 This article explains more: https://community.jboss.org/wiki/EqualsandHashCode?_sscc=t这篇文章解释了更多: https : //community.jboss.org/wiki/EqualsandHashCode?_sscc=t

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM