2009-10-28 119 views
78

应该如何模型类的equals和hashCode在Hibernate中实现实施?常见的缺陷是什么?大多数情况下,默认实现是否足够好?使用业务密钥有什么意义吗?应该如何equals和hashCode使用JPA时和Hibernate

在我看来,这是相当困难的得到它的权利,在任何情况下,当延迟抓取,ID生成,代理等都是考虑的工作。

+0

另请参见http://stackoverflow.com/a/39827962/548473(spring-data-jpa的实现) – GKislin 2016-10-03 09:07:25

回答

55

具有休眠的时候/如何documentation

覆盖equals()/hashCode()一个漫长且愉快描述它的要点是你唯一需要担心它,如果你的实体将是一个Set,或者如果你的一部分将要分离/附加它的实例。后者并不常见。前者通常是最好的处理通过:

  1. 对业务关键立足equals()/hashCode() - 例如在对象(或至少会话)生命周期中不会更改的属性的唯一组合。
  2. 如果以上是不可能的,则在主键上设置基础equals()/hashCode()如果设置了,则以及其他对象标识/ System.identityHashCode()重要这里的一部分是,你需要重新加载你的设置后新的实体已被添加到它并坚持;否则最终会出现奇怪的行为(最终导致错误和/或数据损坏),因为您的实体可能被分配到与当前的hashCode()不匹配的存储桶。
+1

当你说“重新加载”@ChssPly76你的意思是做一个'refresh()'?你的实体如何遵守'Set'合同,最终会出现在错误的桶中(假设你有足够好的哈希码实现)。 – 2009-10-28 19:21:26

+4

刷新收藏或重新加载整个(所有者)实体,是的。至于错误的桶:a)你添加新的实体来设置,它的ID还没有设置,所以你使用的是identityHashCode,它将你的实体放在bucket#1中。 b)你的实体(在set中)是持久的,它现在有一个id,因此你使用基于该id的hashCode()。它与上面的不同之处在于**会将您的实体放置在第2个桶中。现在,假设你在其他地方持有对此实体的引用,请尝试调用'Set.contains(entity)'并返回'false'。 – ChssPly76 2009-10-28 19:33:57

+0

有道理但从未使用过identityHashCode自己虽然我看到它在Hibernate源代码中使用,就像在他们的ResultTransformers中一样 – 2009-10-29 05:37:28

4

是的,这很难。在我的项目中,equals和hashCode都依赖于对象的id。这个解决方案的问题是,如果对象还没有被保存,它们都不会工作,因为id是由数据库生成的。在我的情况下,这是可以容忍的,因为在几乎所有情况下,对象都会立即被持久化。除此之外,它很好用,很容易实现。

+0

我认为我们所做的就是在没有生成id的情况下使用对象标识 – 2009-10-28 17:22:40

+2

问题这里是,如果你坚持对象,你的哈希码改变。如果对象已经是基于散列的数据结构的一部分,那么这可能会产生很大的不利结果。因此,如果你确实使用了对象标识,那么最好继续使用obj id,直到对象完全释放(或者从任何基于散列的结构中删除对象,然后将其添加回去)。就个人而言,我认为最好不要使用id,并将哈希基于对象的不可变属性。 – 2009-10-29 03:55:08

29

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

要回答原来的问题:

是默认的实现大多数情况不够好?

答案是肯定的,它是大多数情况下。

你只需要重写equals()hashcode()如果实体将在Set使用(这是很常见)实体将被分离,并随后重新附着,休眠会话(这是一个罕见的休眠使用)。

接受的答案表示如果条件为真,则需要重写方法。

+0

这与我的观察一致,有时间找出[为什么](http://docs.jboss.org/hibernate/core/4.0/manual/en-US/html/persistent -classes.html#持久类-equalshashcode)。 – 2016-02-11 16:31:24

+0

“如果实体将在Set中使用,则只需要覆盖equals()和hashcode()就足够了,如果某些字段标识对象,并且您不想依赖Object.equals()识别对象。 – davidxxx 2017-11-11 21:23:58

11

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

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

改用:

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

这也是一个很好的做法,因为在Implementing equals in Java Practices解释。

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

+2

什么解释,谢谢先生访问地球.. – 2015-02-25 08:18:56

+0

如果你正在比较具体类的对象,这在我的情况不起作用,这工作。我正在比较超类的对象,在这种情况下,此代码适用于我:obj1.getClass()。isInstance(obj2) – Tad 2015-08-07 19:38:42

9

最好的equals/hashCode实施是当你使用unique business key

商业密钥应该在所有entity state transitions(瞬态,附加,分离,删除)中保持一致,这就是为什么你不能依靠id来实现平等。

另一种选择是切换到使用由应用程序逻辑分配的UUID identifiers。这样,您可以使用UUID作为equals/hashCode,因为在实体被刷新之前分配了id。

您甚至可以使用equalshashCode实体标识符,但需要你总是返回相同hashCode值,以便您确保实体的hashCode值是所有实体状态转变相一致。检出this post for more on this topic

+0

+1用于uuid方法。把它放到'BaseEntity'中,不要再考虑这个问题。它需要在数据库方面的一点空间,但你更好的价格支付的安慰:) – 2016-08-25 13:49:55

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 
} 

} 
2

如果你碰巧覆盖equals,确保履行合同: -

  • 对称性
  • 反光
  • 传递性
  • 非零

,并覆盖hashCode,作为其合同依靠equals实施。

Joshua Bloch(Collection框架的设计者)强烈要求遵循这些规则。

  • 项目9:始终重写了hashCode当您覆写等于

有严重的时候,你不遵循这些合同意想不到的效果。例如,List.contains(Object o)可能因为未履行一般合同而返回错误的boolean价值。

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方法比较仅在第一次保持对象时生成的ID,则会遇到麻烦。当等号被呼叫时,他们可能不在那里。