我已经建立了一个单向的 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
...
我找到了两个临时解决方案:
- 删除 ServiceOrder equals() 和 hashCode(); 或
- 建立双向关系(当然,每次添加/删除时都会更新双方);
我不明白这种行为。如果关系是单向或双向的,为什么会有不同的待遇?此外,如果我在同一事务的上下文中获取这些实体,第一个 equals 测试将如何失败:
if ( this == obj )
return true;
我正在使用 JPA 2.1 (Wildfly 8.1.0)。
此致,并提前致谢。 热南
最佳答案
您应该重写 equals 和 hashCode,但您永远不应该将 ID 用于哈希码,除非您使 hashCode 不可变并仅使用 ID when it's not null for equality .
否则,在保存 ID 为 null 的实体之前,当您将 Transient 实体添加到集合时,将在刷新时间内分配该 ID,当它被持久化并生成 ID 时,equals/hashCode 契约是会坏掉的。
Hibernate 最佳实践建议 using a business key用于对象相等/hashCode。
所以引用引用文档:
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.
To avoid this problem we recommend using the "semi"-unique attributes of your persistent class to implement equals() (and 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. Of course, you may also use the database identifier as a convenient read-only handle, e.g. to build links in web applications.
Instead of using the database identifier for the equality comparison, you should use a set of properties for equals() that identify your individual objects. 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. 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!
关于java - 单向@OneToMany 关联未通过 JPA 中的相等性测试,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/24877814/