Hibernate:什么时候需要实现 equals() 和 hashCode(),如果需要,如何实现?

发布于 2024-12-29 22:27:35 字数 1905 浏览 5 评论 0原文

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

基本上我想避免像该链接中的 HashMap key 问题这样的情况,或者像下面这样:

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

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

不管怎样,我现在在一家 Hibernate 商店,我那些更精通 Hibernate 的同事告诉我这种方法行不通。具体来说,该声明似乎是在以下场景中——Hibernate

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

-- 除非 h1.equals(h4) (或者可能是 h4.equals(h1),我不清楚,但我希望它是可传递的,无论如何), Hibernate 将无法区分这些是同一件事,并且会发生不好的事情。

所以,我想知道的是:

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

注意:我已阅读在 Hibernate 文档中实现 equals() 和 hashCode() ,它至少不能解决我担心的情况直接,也没有详细解释 Hibernate 真正需要的 equals() 和 hashCode() 功能。 Hibernate 中的 equals 和 hashcode 的答案也不行,否则我就不会打扰发布此内容。

Based on various bad experiences my rule of thumb as a Java programmer is to only implement equals() and hashCode() on immutable objects, where two instances of the object really are interchangeable.

Basically I want to avoid situations like the HashMap key problem in that link, or like the following:

  1. Get a thing with a certain identity.
  2. Modify it.
  3. Add it to a set.
  4. (later) Get another thing with the same identity.
  5. Modify it.
  6. Add it to the same set.
  7. Fail to notice that this add doesn't actually happen, since the set thinks the thing is already there.
  8. Do something with the things in the set.
  9. Fail to notice that the change from step (5) is ignored, and we still have the state from step (2).

And by and large over the course of my Java career I haven't found a lot of use for equals() except for (1) value objects and (2) putting things into collections. I've also found that immutability + copy-and-modify constructors/builders is generally a much happier world than setters. Two objects might have the same ID and might represent the same logical entity, but if they have different data -- if they represent snapshots of the conceptual entity at different times -- then they're not equal().

Anyway, I'm now in a Hibernate shop, and my more Hibernate-savvy colleagues are telling me this approach isn't going to work. Specifically, the claim seems to be that in the following scenario --

  1. Hibernate loads a thing from the database -- we'll call it instance h1.
  2. This thing is marshaled and sent somewhere via a web service.
  3. The web service client fiddles with it and sends a modified version back.
  4. The modified version is unmarshalled on the server -- we'll call it instance h4.
  5. We want Hibernate to update the database with the modifications.

-- unless h1.equals(h4) (or perhaps h4.equals(h1), I'm not clear, but I would hope it's transitive anyway so whatever), Hibernate will not be able to tell that these are the same thing, and Bad Things Will Happen.

So, what I want to know:

  • Is this true?
  • If so, why? What is Hibernate using equals() for?
  • If Hibernate needs h1 and h4 to be equal, how does it (and how do we) keep track of which one is the modified version?

Note: I've read Implementing equals() and hashCode() in the Hibernate docs and it doesn't deal with the situation I'm worried about, at least directly, nor does it explain in any detail what Hibernate really needs out of equals() and hashCode(). Neither does the answer to equals and hashcode in Hibernate, or I wouldn't have bothered to post this.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

夜未央樱花落 2025-01-05 22:27:35

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

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

Hibernate 文档建议使用业务密钥。但业务密钥应该依赖于唯一标识对象的字段。 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 中的问题,唯一的方法是在添加之前始终询问该集合是否已经存在。然后您可以编写代码来处理这种特殊情况。

First of all, your original idea, that you should implement equals() and hashCode() only on immutable objects, certainly works, but it's stricter than it needs to be. You just need these two methods to rely on immutable fields. Any field whose value may change is unsuitable for use in those two methods, but the other fields need not be immutable.

Having said that, Hibernate knows they're the same object by comparing their primary keys. This leads many people to write those two methods to rely on the primary key. Hibernate docs recommend you don't do it this way, but many people ignore this advice without much trouble. It means you can't add entities to a Set until after they've been persisted, which is a restriction that's not too hard to live with.

Hibernate docs recommend using a business key. But the business key should rely on fields that uniquely identify an object. The Hibernate docs say "use a business key that is a combination of unique, typically immutable, attributes." I use fields that have a unique constraint on them in the database. So, if your Sql CREATE TABLE statement specifies a constraint as

CONSTRAINT uc_order_num_item UNIQUE (order_num, order_item)

then those two fields can be your business key. That way, if you change one of them, both Hibernate and Java will treat the modified object as a different object. Of course, if you do change one of these "immutable" fields, you mess up any Set they belong to. So I guess you need to document clearly which fields comprise the business key, and write your application with the understanding that fields in the business key should never be changed for persisted objects. I can see why people ignore the advice and just use the primary key. But you could define the primary key like this:

CONSTRAINT pk_order_num_item PRIMARY KEY (order_num, order_item)

And you would still have the same problem.

Personally, I would like to see an annotation that specifies every field in the business key, and have an IDE inspection that checks if I modify it for persisted objects. Maybe that's asking too much.

Another approach, one that solves all of these problems, is to use a UUID for the primary key, which you generate on the client when you first construct an unpersisted entity. Since you never need to show it to the user, your code is not likely to change its value once you set it. This lets you write hashCode() and equals() methods that always work, and remain consistent with each other.

One more thing: If you want to avoid the problem of adding an object to a Set that already contains a different (modified) version of it, the only way is to always ask the set if it's already there before adding it. Then you can write code to handle that special case.

○闲身 2025-01-05 22:27:35

JPA/Hibernate 强加了什么语义?

JPA 规范如下所述。

2.4 主键和实体身份

每个实体都必须有一个主键。
...
其主键的值唯一标识持久性上下文中的实体实例以及 EntityManager 操作

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

但是 您引用的 Hibernate 建议(以及我看过的另一篇文章)说不要这样做,而是使用“业务键”而不是主键。这样做的原因似乎是因为我们无法保证实体对象 具有生成的主键的值,直到实体已同步(使用EntityManager.flush())到数据库。

What semantics does JPA/Hibernate impose?

The JPA specification says the following.

2.4 Primary Keys and Entity Identity

Every entity must have a primary key.
...
The value of its primary key uniquely identifies an entity instance within a persistence context and to EntityManager operations

I interpret that as saying the semantics of equivalence for JPA entities is equivalence of primary keys. That suggests the equals() method should compare the primary keys for equivalence, and nothing else.

But the Hibernate advice you reference (and another article I've seen) say not to do that, but rather to use a "business key" rather than the primary key. The reason for this seems to be because we can not guarantee that an entity object has a value for a generated primary key until the entity has been synchronized (using EntityManager.flush()) to the data-base.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文