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

ega*_*aga 93 java orm hibernate equals hashcode

应该如何在Hibernate中实现模型类的equals和hashcode?常见的陷阱是什么?对于大多数情况,默认实现是否足够好?使用商业密钥有什么意义吗?

在我看来,当考虑到懒惰的提取,id生成,代理等时,很难在任何情况下都能正常工作.

Chs*_*y76 66

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

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

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

  • 刷新集合或重新加载整个(所有者)实体,是的.至于错误的存储桶:a)你添加新的实体来设置,它的id还没有设置,所以你使用identityHashCode将你的实体放在桶#1中.b)你的实体(在集合中)是持久的,它现在有一个id,因此你正在使用基于该id的hashCode().它与上面不同,**会将你的实体放在#2桶中.现在,假设您在其他地方持有对此实体的引用,请尝试调用`Set.contains(entity)`并且您将返回`false`.get()/ put()/ etc也一样...... (4认同)

小智 34

我不认为接受的答案是准确的.

回答原来的问题:

对于大多数情况,默认实现是否足够好?

答案是肯定的,在大多数情况下都是如此.

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

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


Vla*_*cea 15

最佳equals/ hashCode实施是使用唯一的业务密钥.

业务键应该在所有实体状态转换(瞬态,附加,分离,删除)之间保持一致,这就是为什么不能依赖id进行相等的原因.

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

您甚至可以使用实体标识符equalshashCode,但这要求您始终返回相同的hashCode值,以确保实体hashCode值在所有实体状态转换中保持一致.有关此主题的更多信息,请查看此帖子.


sti*_*vlo 12

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

if (getClass() != that.getClass()) return false;
Run Code Online (Sandbox Code Playgroud)

改为使用:

if (!(otherObject instanceof Unit)) return false;
Run Code Online (Sandbox Code Playgroud)

这也是一种很好的做法,正如Java Practices中实现equals所解释的那样.

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


Car*_*los 6

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

  • 这里的问题是,如果你持久化对象,你的哈希码就会改变.如果对象已经是基于散列的数据结构的一部分,那么这可能会产生很大的不利结果.因此,如果您最终使用对象标识,则最好继续使用obj id,直到对象完全释放(或从任何基于散列的结构中删除对象,保留,然后将其重新添加).就个人而言,我认为最好不要使用id,并将哈希基于对象的不可变属性. (2认同)