基于various bad experiences我作为Java程序员的经验法则是仅对不可变对象实现equals()
和hashCode()
,其中对象的两个实例确实可以互换。
基本上我想避免该链接中出现HashMap
密钥问题,或者如下所示:
在我的Java职业生涯中,我haven't found a lot of use为equals()
除了(1)值对象和(2)将事物放入集合中。我还发现,不变性+复制和修改构造函数/构建器通常比定位器更快乐。两个对象可能具有相同的ID,可能代表相同的逻辑实体,但如果它们具有不同的数据 - 如果它们代表概念实体在不同时间的快照 - 则它们不是equal()
。
无论如何,我现在在Hibernate商店,而我更熟悉Hibernate的同事告诉我这种方法不会起作用。具体而言,索赔似乎是在以下情形中 -
h1
。h4
。 - 除非h1.equals(h4)
(或者h4.equals(h1)
,我不清楚,但我希望无论如何它都是传递性的,Hibernate将无法分辨出这些是同一件事,坏事将会发生。
所以,我想知道的是:
equals()
的Hibernate?h1
和h4
相等,那么它(以及我们如何)跟踪哪一个是修改后的版本? 注意:我在Hibernate文档中读过Implementing equals() and hashCode()并且它没有处理我担心的情况,至少是直接处理,也没有在任何情况下解释详细介绍了Hibernate在equals()
和hashCode()
中真正需要的内容。 equals and hashcode in Hibernate的回答也没有,或者我不愿意发布这个。
答案 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()
)到数据库。