单向@OneToMany关联在JPA中失败了相等性测试

时间:2014-07-22 02:11:11

标签: java hibernate jpa orm wildfly

我已经建立了一个单向的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;
    }
}

ServiceOrder类使用其自动生成的long id实现hashCode和equals。它们是由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;
    }
...
}

表格都是按预期自动生成的。然后,当我想建立我的关系时:

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

到目前为止,一切都很好,事务成功提交。当我尝试删除关系时出现问题(在另一个事务中,另一个时刻):

...
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
...

我调试并发现ServiceOrder.equals()中的以下内容失败:

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

我找到了两个临时解决方案:

  1. 删除ServiceOrder equals()和hashCode(); 或
  2. 使关系成为双向关系(当然,每次添加/删除都会更新双方);
  3. 我不明白这种行为。如果这种关系是单向的还是双向的,为什么治疗上存在差异?此外,如果我在同一个事务的上下文中获取这些实体,那么第一个等于测试的失败方式如何:

    if ( this == obj )
        return true;
    

    我使用的是JPA 2.1(Wildfly 8.1.0)。

    最诚挚的问候,并提前感谢您。 Renan的

2 个答案:

答案 0 :(得分:3)

你应该覆盖equals和hashCode,但是除非你使hashCode成为不可变的,否则你永远不应该使用该ID,只有当它不是等于null时才使用ID,如in this article所述。

否则,在保存在刷新时间期间要分配的ID为null的实体之前,当您将一个Transient实体添加到集合时,当它被持久化并且生成ID时,equals / hashCode合约是破碎了。

Hibernate最佳实践建议using a business key用于对象相等/ hashCode。

所以引用参考文档:

  

一般合同是:如果要将对象存储在List,Map中   或者一个Set然后它是equals和hashCode的要求   实施,以便他们遵守规定的标准合同   文档。

     

为避免此问题,我们建议使用“半” - 唯一属性   你的持久化类实现equals()(和hashCode())。   基本上你应该认为你的数据库标识符没有   商业意义(记住,代理标识符属性和   无论如何都推荐自动生成的值)。数据库   标识符属性应该只是一个对象标识符,基本上   应该只由Hibernate使用。当然,你也可以使用   数据库标识符作为方便的只读句柄,例如建立   Web应用程序中的链接。

     

而不是使用数据库标识符进行相等   比较,你应该使用equals()的一组属性   识别您的个人对象。例如,如果你有一个“项目”   类和它有一个“名称”字符串和“创建”日期,我可以使用两者   实现一个好的equals()方法。无需使用持久性   标识符,所谓的“业务密钥”要好得多。它是   自然键,但这次使用它没有错!

答案 1 :(得分:0)

请勿覆盖equalshashCode。 Hibernate有自己的实现来查找对象,这就是为什么你没有得到预期的结果。 本文解释更多: https://community.jboss.org/wiki/EqualsandHashCode?_sscc=t