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

时间:2012-02-01 00:49:25

标签: hibernate equals hashcode

基于various bad experiences我作为Java程序员的经验法则是仅对不可变对象实现equals()hashCode(),其中对象的两个实例确实可以互换。

基本上我想避免该链接中出现HashMap密钥问题,或者如下所示:

  1. 获得具有特定身份的东西。
  2. 修改它。
  3. 将其添加到一个集合中。
  4. (稍后)获得具有相同身份的另一件事。
  5. 修改它。
  6. 将其添加到同一组中。
  7. 没有注意到这个添加实际上并没有发生,因为该集合认为该东西已经存在。
  8. 对集合中的事物做点什么。
  9. 未注意到步骤(5)的更改被忽略,我们仍然处于步骤(2)的状态。
  10. 在我的Java职业生涯中,我haven't found a lot of useequals() 除了(1)值对象和(2)将事物放入集合中。我还发现,不变性+复制和修改构造函数/构建器通常比定位器更快乐。两个对象可能具有相同的ID,可能代表相同的逻辑实体,但如果它们具有不同的数据 - 如果它们代表概念实体在不同时间的快照 - 则它们不是equal()

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

    1. Hibernate从数据库加载一个东西 - 我们称之为实例h1
    2. 这个东西被编组并通过网络服务发送到某个地方。
    3. 网络服务客户端摆弄它并发回修改后的版本。
    4. 修改后的版本在服务器上解组 - 我们称之为实例h4
    5. 我们希望Hibernate通过修改来更新数据库。
    6. - 除非h1.equals(h4)(或者h4.equals(h1),我不清楚,但我希望无论如何它都是传递性的,Hibernate将无法分辨出这些是同一件事,坏事将会发生。

      所以,我想知道的是:

      • 这是真的吗?
      • 如果是这样,为什么?什么是使用equals()的Hibernate?
      • 如果Hibernate需要h1h4相等,那么它(以及我们如何)跟踪哪一个是修改后的版本?

      注意:我在Hibernate文档中读过Implementing equals() and hashCode()并且它没有处理我担心的情况,至少是直接处理,也没有在任何情况下解释详细介绍了Hibernate在equals()hashCode()中真正需要的内容。 equals and hashcode in Hibernate的回答也没有,或者我不愿意发布这个。

2 个答案:

答案 0 :(得分:4)

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

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

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

CONSTRAINT uc_order_num_item UNIQUE (order_num, order_item)

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

CONSTRAINT pk_order_num_item PRIMARY KEY (order_num, order_item)

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

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

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

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

答案 1 :(得分:1)

  

JPA / Hibernate强加什么语义?

JPA规范说明如下。

  

2.4主要密钥和实体身份

     

每个实体都必须有一个主键。   ...   其主键的值唯一标识持久化上下文和EntityManager操作

中的实体实例

我解释说,JPA实体的等价语义是主键的等价。这表明equals()方法应该比较等价的主键,而不是其他。

但是您引用的Hibernate advice(以及我见过的另一篇文章)说不要这样做,而是使用“业务密钥”而不是主键。这样做的原因似乎是因为我们不能保证实体对象has a value for a generated primary key直到实体已经被同步(使用EntityManager.flush())到数据库。