Hibernate:什么时候需要实现equals()和hashCode(),如果是的话,怎么做?

Dav*_*les 7 hibernate equals hashcode

基于各种糟糕的经历,我作为Java程序员的经验法则只是实现equals()hashCode()不可变对象,其中对象的两个实例确实是可以互换的.

基本上我想避免像HashMap链接中的关键问题那样的情况,或者如下所示:

  1. 获得具有特定身份的东西.
  2. 修改它.
  3. 将它添加到一个集合中.
  4. (稍后)获得具有相同身份的另一件事.
  5. 修改它.
  6. 将其添加到同一组.
  7. 没有注意到这个添加实际上并没有发生,因为该集认为事情已经存在.
  8. 对集合中的东西做点什么.
  9. 没有注意到步骤(5)的变化被忽略,我们仍然有步骤(2)的状态.

在我的Java职业生涯中,除了(1)值对象和(2)将东西放入集合之外,我还没有找到很多用处.我还发现,不变性+复制和修改构造函数/构建器通常比定位器更快乐.两个对象可能具有相同的ID,并且可能表示相同的逻辑实体,但如果它们具有不同的数据 - 如果它们在不同时间表示概念实体的快照 - 则它们不是.equals() equal()

无论如何,我现在在Hibernate商店,我更熟悉Hibernate的同事告诉我这种方法不会起作用.具体而言,声称似乎是在以下情况中 -

  1. Hibernate从数据库加载一个东西 - 我们称之为实例h1.
  2. 这个东西被编组并通过Web服务发送到某个地方.
  3. Web服务客户端对其进行了调整并返回修改后的版本.
  4. 修改后的版本在服务器上解组 - 我们称之为实例h4.
  5. 我们希望Hibernate通过修改来更新数据库.

-除非h1.equals(h4)(或许h4.equals(h1),我不太清楚,但我希望它是传递反正所以不管),Hibernate将不能够告诉这些都是一样的东西,坏的事情会发生.

那么,我想知道的是:

  • 这是真的?
  • 如果是这样,为什么?Hibernate equals()用于什么?
  • 如果Hibernate需要h1并且h4相同,它是如何(以及我们如何)跟踪哪一个是修改版本?

注:我读过实现equals()和hashCode()在Hibernate文档,它不处理,我担心的情况来看,至少直接,也没有任何细节什么的Hibernate真正需要出来解释equals()hashCode().Hibernate中的equals和hashcode的答案也没有,或者我不打算发布这个.

Orl*_*ree 5

首先,你最初的想法,你应该只在不可变对象上实现 equals() 和 hashCode(),当然可以工作,但它比它需要的更严格。您只需要这两种方法即可依赖不可变字段。任何值可能改变的字段都不适合在这两种方法中使用,但其他字段不必是不可变的。

话虽如此,Hibernate 通过比较它们的主键知道它们是同一个对象。这导致很多人写这两个方法来依赖主键。Hibernate 文档建议你不要这样做,但很多人忽略了这个建议,没有太多麻烦。这意味着你不能在实体被持久化之前将实体添加到集合中,这是一个不太难接受的限制。

Hibernate 文档建议使用业务密钥。但是业务键应该依赖于唯一标识一个对象的字段。Hibernate 文档说“使用由独特的、通常不可变的属性组合而成的业务密钥。” 我使用在数据库中对它们具有唯一约束的字段。因此,如果您的 Sql CREATE TABLE 语句将约束指定为

CONSTRAINT uc_order_num_item UNIQUE (order_num, order_item)
Run Code Online (Sandbox Code Playgroud)

那么这两个字段可以成为您的业务关键。这样,如果您更改其中之一,Hibernate 和 Java 都会将修改后的对象视为不同的对象。当然,如果您确实更改了这些“不可变”字段之一,则会弄乱它们所属的任何 Set。因此,我猜您需要清楚地记录哪些字段包含业务密钥,并在编写应用程序时了解永远不应为持久对象更改业务密钥中的字段。我可以理解为什么人们忽略建议而只使用主键。但是你可以像这样定义主键:

CONSTRAINT pk_order_num_item PRIMARY KEY (order_num, order_item)
Run Code Online (Sandbox Code Playgroud)

你仍然会遇到同样的问题。

就我个人而言,我希望看到一个注释,它指定了业务键中的每个字段,并有一个 IDE 检查来检查我是否针对持久对象修改了它。也许这要求太多了。

另一种解决所有这些问题的方法是使用 UUID 作为主键,当您第一次构造非持久化实体时,您会在客户端上生成该 UUID。由于您永远不需要向用户显示它,因此一旦您设置它,您的代码就不太可能更改其值。这使您可以编写始终有效且彼此保持一致的 hashCode() 和 equals() 方法。

还有一件事:如果你想避免将一个对象添加到一个已经包含它的不同(修改)版本的 Set 的问题,唯一的方法是在添加它之前总是询问它是否已经存在。然后您可以编写代码来处理这种特殊情况。