使用JPA和Hibernate时应如何实现equals和hashcode


102

模型类的等式和哈希码应如何在Hibernate中实现?有哪些常见的陷阱?默认实现在大多数情况下是否足够好?使用业务密钥是否有意义?

在我看来,要考虑到延迟获取,ID生成,代理等,在每种情况下都无法正确工作。


Answers:


73

Hibernate有何时/如何重写一个很好的和长期的描述equals()/ hashCode()文档

要点是,如果您的实体将成为a的一部分,Set或者您要分离/附加其实例,则只需要担心它。后者并不常见。前者通常最好通过以下方式处理:

  1. 立足 equals() / hashCode()基于业务密钥-例如,在对象(或至少会话)生存期内不会改变的属性的唯一组合。
  2. 如果上述是不可能的,基equals()/ hashCode()对主键如果它被设置和对象标识/ System.identityHashCode()否则。这里的重要部分是您需要在将新实体添加到其中并保留之后重新加载您的Set;否则,您可能会遇到奇怪的行为(最终导致错误和/或数据损坏),因为您的实体可能会分配给与当前桶不匹配的存储桶hashCode()

1
当您说“重新加载” @ ChssPly76时,您的意思是做一个refresh()?遵守Set合同的实体会如何落入错误的存储桶中(假设您具有足够好的哈希码实现)。
非续集,09年

4
刷新集合或重新加载整个(所有者)实体,是的。就错误的存储桶而言:a)您添加了要设置的新实体,但尚未设置其ID,因此您使用的是identityHashCode,它将您的实体放置在存储桶#1中。b)您的实体(在集合内)是持久的,它现在确实具有一个ID,因此您基于该ID使用hashCode()。与上面的不同,它会将您的实体放在第二个存储桶中。现在,假设您在其他地方拥有对该实体的引用,请尝试致电,Set.contains(entity)然后您会回来false。同样适用于get()/ put()/等...
ChssPly76

有道理,但我自己从未使用过identityHashCode,尽管我看到它在Hibernate源代码中使用过,就像在他们的ResultTransformers中一样
非续集,2009年

1
使用Hibernate时,您可能还会遇到这个问题,但我仍然没有找到解决方案。
Giovanni Botta

@ ChssPly76由于确定两个对象是否相等的业务规则,我需要将equals / hashcode方法建立在对象生命周期内可能会更改的属性的基础上。这真的很重要吗?如果是这样,我该如何解决?
ubiquibacon

39

我认为接受的答案不正确。

要回答原始问题:

默认实现在大多数情况下是否足够好?

答案是肯定的,在大多数情况下是这样。

你只需要重写equals()hashcode()该实体将被用在Set(这是很常见)该实体将与休眠会话分离,然后再重新附加到休眠会话(这是休眠的一种罕见用法),则只需重写。

接受的答案表明,如果任一条件为真,则需要覆盖这些方法。


这符合我的观察,是时候找出原因了
VlastimilOvčáčík16年

如果某些字段标识一个对象,“如果实体将在集合中使用,则只需要覆盖equals()和hashcode()”就足够了,因此您不想依赖Object.equals()来标识对象。
davidxxx '17

17

最好的equals/ hashCode实施方法是使用唯一的业务密钥

业务密钥在所有实体状态转换(瞬态,附加,分离,删除)之间应该保持一致,这就是为什么不能依靠id来实现相等性的原因。

另一个选择是切换到使用由应用程序逻辑分配的UUID标识符。这样,您可以将UUID用作equals/,hashCode因为在刷新实体之前已分配ID。

您甚至可以将实体标识符用于equalshashCode,但是这要求您始终返回相同的hashCode值,以确保实体hashCode值在所有实体状态转换之间都是一致的。请查看此帖子以获取有关此主题的更多信息


+1为uuid方法。把它放进去BaseEntity,再也不要考虑这个问题了。在数据库侧需要一些空间,但是您最好为舒适度付出代价:)
Martin Frey

12

通过延迟加载加载实体时,它不是基本类型的实例,而是由javassist生成的动态生成的子类型,因此对同一类类型的检查将失败,因此请不要使用:

if (getClass() != that.getClass()) return false;

改为使用:

if (!(otherObject instanceof Unit)) return false;

正如在Java实践中实现平等所解释的,这也是一个好习惯

由于相同的原因,直接访问字段可能无法工作并返回null而不是基础值,因此不要对属性使用比较,而要使用getter,因为它们可能触发加载基础值。


1
如果您要比较具体类的对象(在我的情况下不起作用),则此方法有效。我正在比较超类的对象,在这种情况下,此代码对我有用:obj1.getClass()。isInstance(obj2)
Tad

6

是的,很难。在我的项目中,equals和hashCode都依赖于对象的ID。该解决方案的问题在于,如果该对象尚未持久化,则它们都不起作用,因为该ID是由数据库生成的。在我的情况下,这是可以容忍的,因为在几乎所有情况下,对象都是立即保留的。除此之外,它的工作原理非常好并且易​​于实现。


我认为我们要做的是在未生成ID的情况下使用对象身份
Kathy Van Stone 2009年

2
这里的问题是,如果您保留对象,则您的哈希码会更改。如果对象已经是基于哈希的数据结构的一部分,则可能会产生很大的不利结果。因此,如果确实使用对象标识结束了,则最好继续使用obj id,直到对象完全释放为止(或从任何基于哈希的结构中删除该对象,保留该对象,然后再添加回去)。就个人而言,我认为最好不要使用id,而将哈希基于对象的不可变属性。
凯文·戴

1

在Hibernate 5.2的文档中,它说您可能不希望实现hashCode并且完全等于-取决于您的情况。

https://docs.jboss.org/hibernate/orm/5.2/userguide/html_single/Hibernate_User_Guide.html#mapping-model-pojo-equalshashcode

通常,如果在数据库中相等,则从同一会话加载的两个对象将相等(不实现hashCode和equals)。

如果您使用两个或多个会话,它将变得很复杂。在这种情况下,两个对象的相等性取决于您的equals-method实现。

此外,如果您的equals-method比较仅在第一次持久存储对象时生成的ID,您会遇到麻烦。当调用equals时,它们可能还不存在。


0

这里有一篇很好的文章:https : //docs.jboss.org/hibernate/stable/core.old/reference/en/html/persistent-classes-equalshashcode.html

引用文章中的重要内容:

我们建议使用业务键相等性来实现equals()和hashCode()。业务密钥相等性意味着equals()方法仅比较构成业务密钥的属性,该属性将标识我们在现实世界中的实例(自然的候选密钥):

简单来说

public class Cat {

...
public boolean equals(Object other) {
    //Basic test / class cast
    return this.catId==other.catId;
}

public int hashCode() {
    int result;

    return 3*this.catId; //any primenumber 
}

}

0

如果您碰巧要超越equals,请确保您履行其合同:-

  • 对称
  • 反思性的
  • 过渡性
  • 一致的
  • 非空

并覆盖hashCode,因为其合同依赖equals实施。

Joshua Bloch(集合框架的设计者)强烈敦促遵守这些规则。

  • 第9项:覆盖等于时始终覆盖hashCode

不遵守这些合同会产生严重的意想不到的影响。例如,由于未履行总合同,List#contains(Object o)可能返回错误的boolean值。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.