java 覆盖持久实体的 hashCode() 和 equals() 方法的正确方法是什么?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1928191/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-29 18:34:10  来源:igfitidea点击:

What is the correct way of overriding hashCode () and equals () methods of persistent entity?

javahibernatejpajakarta-ee

提问by Roman

I have a simple class Role:

我有一个简单的类角色:

@Entity
@Table (name = "ROLE")
public class Role implements Serializable {

    @Id
    @GeneratedValue
    private Integer id;
    @Column
    private String roleName;

    public Role () { }

    public Role (String roleName) {
        this.roleName = roleName;
    }

    public void setId (Integer id) {
        this.id = id;
    }

    public Integer getId () {
        return id;
    }

    public void setRoleName (String roleName) {
        this.roleName = roleName;
    }

    public String getRoleName () {
        return roleName;
    }
}

Now I want to override its methods equals and hashCode. My first suggestion is:

现在我想覆盖它的方法 equals 和 hashCode。我的第一个建议是:

public boolean equals (Object obj) {
    if (obj instanceof Role) {
        return ((Role)obj).getRoleName ().equals (roleName);
    }
    return false;
}

public int hashCode () {
    return id; 
}

But when I create new Role object, its id is null. That's why I have some problem with hashCode method implementation. Now I can simply return roleName.hashCode ()but what if roleName is not necessary field? I'm almost sure that it's not so difficult to make up more complicated example which can't be solved by returning hashCode of one of its fields.

但是当我创建新的 Role 对象时,它的 id 为空。这就是为什么我对 hashCode 方法实现有一些问题。现在我可以简单地返回roleName.hashCode ()但是如果 roleName 不是必需的字段怎么办?我几乎可以肯定,通过返回其字段之一的 hashCode 无法解决的更复杂的示例并不难。

So I'd like to see some links to related discussions or to hear your experience of solving this problem. Thanks!

所以我想看到一些相关讨论的链接,或者听听你解决这个问题的经验。谢谢!

采纳答案by Nathan Hughes

Bauer and King's book Java Persistence with Hibernateadvises against using the key field for equals and hashCode. They advise you should pick out what would be the object's business key fields (if there was no artificial key) and use those to test equality. So in this case if role name was not a necessary field you would find the fields that were necessary and use them in combination. In the case of the code you post where rolename is all you have besides the id, rolename would be what I'd go with.

Bauer 和 King 的著作Java Persistence with Hibernate建议不要使用 equals 和 hashCode 的键字段。他们建议您应该挑选出对象的业务关键字段(如果没有人工密钥)并使用它们来测试相等性。因此,在这种情况下,如果角色名称不是必需字段,您将找到必需的字段并将它们组合使用。对于您发布的代码,除了 id 之外,rolename 就是您所拥有的全部,rolename 将是我要使用的。

Here's a quote from page 398:

这是第 398 页的引述:

We argue that essentially every entity class should have somebusiness key, even if it includes all properties of the class (this would be appropriate for some immutable classes). The business key is what the user things of as uniquely identifying a particular record, whereas the surrogate key is what the application and database use.

Business key equalitymeans that the equals() method compares only the properties that form the business key. This is a perfect solution that avoids all the problems presented earlier. The only downside is that it requires extra thought to identify the correct business key in the first place. This effort is required anyway; it's important to identify any unique keys if your database must ensure data integrity via constraint checking.

我们认为本质上每个实体类都应该有一些业务键,即使它包含类的所有属性(这适用​​于某些不可变类)。业务键是用户唯一标识特定记录的东西,而代理键是应用程序和数据库使用的东西。

业务键相等意味着 equals() 方法只比较构成业务键的属性。这是一个完美的解决方案,可以避免之前出现的所有问题。唯一的缺点是首先需要额外考虑才能确定正确的业务密钥。无论如何,这种努力是必需的;如果您的数据库必须通过约束检查来确保数据完整性,那么识别任何唯一键很重要。

An easy way I use to construct an equals and hashcode method is to create a toString method that returns the values of the 'business key' fields, then use that in the equals() and hashCode() methods. CLARIFICATION: This is a lazy approach for when I am not concerned about performance (for instance, in rinky-dink internal webapps), if performance is expected to be an issue then write the methods yourself or use your IDE's code generation facilities.

我用来构造 equals 和 hashcode 方法的一种简单方法是创建一个 toString 方法,该方法返回“业务键”字段的值,然后在 equals() 和 hashCode() 方法中使用它。澄清:当我不关心性能时(例如,在 rinky-dink 内部 web 应用程序中),如果预计性能会成为问题,那么这是一种懒惰的方法,然后自己编写方法或使用 IDE 的代码生成工具。

回答by CPerkins

I'm sorry to jump in late with criticism, but nobody else has mentioned it and there is a serious flaw here. Possibly two, actually.

我很抱歉迟到了批评,但没有其他人提到它,这里有一个严重的缺陷。实际上可能是两个。

First, others have mentioned how to handle the possibility of null, but one critical element of a good hashcode()and equals()method pair is that they must obey the contract, and your code above does not do this.

首先,其他人提到了如何处理 null 的可能性,但是好的hashcode()equals()方法对的一个关键要素是它们必须遵守约定,而你上面的代码没有这样做。

The contract is that objects for which equals()returns true must return equal hashcode values, but in your class above, the fields id and roleName are independent.

约定是返回 true 的对象equals()必须返回相等的 hashcode values,但在上面的类中,字段 id 和 roleName 是独立的。

This is fatally flawed practice: you could easily have two objects with the same roleName value, but different id values.

这是一个有致命缺陷的做法:很容易让两个对象具有相同的 roleName 值,但具有不同的 id 值。

The practice is to use the same fields to generate the hashcode value as are used by the equals() method, and in the same order. Below is my replacement for your hashcode method:

实践是使用与 equals() 方法使用相同的字段来生成哈希码值,并且顺序相同。以下是我对您的哈希码方法的替代:


public int hashCode () {
    return ((roleName==null) ? 0 : roleName.hashcode()); 
}

Note: I don't know what you intended by the use of the id field as hashcode, or what you meant to do with the id field. I see from the annotation that it's generated, but it's externallygenerated, so the class as written fails to fulfill the contract.

注意:我不知道您使用 id 字段作为哈希码的目的是什么,或者您打算对 id 字段做什么。我从注释中看到它是生成的,但它是外部生成的,因此编写的类无法履行合同。

If for some reason you find yourself in a situation where this class is exclusivelymanaged by another which faithfully generates "id" values for roleNames which do fulfill the contract, you wouldn't have a functionality problem, but it would still be bad practice, or at least have what people refer to as a "code smell". Besides the fact that there's nothing in the class definition to guarantee that the class is only usable in that way, hashcodes aren't ids, so ids aren't hashcodes.

如果由于某种原因,您发现自己处于此类由另一个忠实地为满足合同的 roleNames 生成“id”值的专门管理的情况,您不会有功能问题,但这仍然是不好的做法,或者至少有人们所说的“代码气味”。除了类定义中没有任何内容来保证该类只能以这种方式使用之外,哈希码不是 id,因此 id 不是 hashcodes

That doesn't mean you couldn't use a guaranteed-equal-for-equal-rolename-values identifier asthe hashcode, but they're not conceptually the same, so at the very least, you should have a block of comment to explain your departure from expected practice.

这并不意味着您不能使用保证相等的角色名称值标识符作为哈希码,但它们在概念上并不相同,因此至少,您应该有一个注释块解释你偏离预期的做法。

And as a good general rule, if you find yourself having to do that, you've probably made a design error. Not always, but probably. One reason for that? People don't always read comments, so even if you create a perfectly functioning system, over time, someone will "misuse" your class and cause problems.

作为一个很好的一般规则,如果你发现自己不得不这样做,你可能犯了一个设计错误。不总是,但可能。原因之一?人们并不总是阅读评论,因此即使您创建了一个完美运行的系统,随着时间的推移,有人会“滥用”您的课程并导致问题。

Having the class itself manage the generation of hashcode values avoids that. And you could still save and make available the externally generated id, for whatever purpose you use it.

让类本身管理哈希码值的生成可以避免这种情况。您仍然可以保存并提供外部生成的 id,无论您出于何种目的使用它。

回答by Brian Deterling

The business key of an object may require its parent (or another one-to-one or many-to-one) relation. In those cases, calling equals() or hashcode() could result in a database hit. Aside from performance, if the session is closed that will cause an error. I mostly gave up trying to use business keys; I use the primary id and avoid using un-saved entities in maps and sets. Has worked well so far but it probably depends on the app (watch out when saving multiple children through the parent cascade). Occasionally, I'll use a separate meaningless key field that's a uuid auto-generated in the constructor or by the object creator.

对象的业务键可能需要其父(或另一个一对一或多对一)关系。在这些情况下,调用 equals() 或 hashcode() 可能会导致数据库命中。除了性能之外,如果会话关闭也会导致错误。我基本上放弃了尝试使用业务密钥;我使用主 ID 并避免在地图和集合中使用未保存的实体。到目前为止运行良好,但它可能取决于应用程序(通过父级联保存多个孩子时要小心)。有时,我会使用一个单独的无意义的键字段,它是在构造函数中或由对象创建者自动生成的 uuid。

回答by Michael

Please find below simple instructions how to create hashCode, equals and toString methods using Apache commons builders.

请在下面找到如何使用 Apache 公共构建器创建 hashCode、equals 和 toString 方法的简单说明。

hashCode

哈希码

  1. If two objects are equal according to the equals() method, they must have the same hashCode() value
  2. It is possible that two distinct objects could have the same hashCode().
  3. Please use unique Business ID for the hashCode creation (it is mean that you should use some unique property that represent business entity, for example, name)
  4. Hibernate Entity: please do NOT use Hibernate id for the hashCode creation
  5. You may call for .appendSuper(super.hashCode()) in case your class is subclass

    @Override
    public int hashCode() {
        return new HashCodeBuilder()
                .append(getName())
                .toHashCode();
    }
    
  1. 如果两个对象根据 equals() 方法相等,则它们必须具有相同的 hashCode() 值
  2. 两个不同的对象可能具有相同的 hashCode()。
  3. 请使用唯一的业务 ID 来创建 hashCode(这意味着您应该使用一些代表业务实体的唯一属性,例如,名称)
  4. Hibernate Entity:请不要使用 Hibernate id 来创建 hashCode
  5. 如果您的类是子类,您可以调用 .appendSuper(super.hashCode())

    @Override
    public int hashCode() {
        return new HashCodeBuilder()
                .append(getName())
                .toHashCode();
    }
    

equals

等于

  1. Please compare Business ID (it is mean that you should use some unique property that represent business entity, for example, name)
  2. Hibernate Entity: please do NOT compare Hibernate id
  3. Hibernate Entity: use getters when you access the other object field to let to Hibernate to load the property
  4. You may call for .appendSuper(super.equals(other)) in case your class is subclass

    @Override
    public boolean equals(final Object other) {
        if (this == other)
            return true;
        if (!(other instanceof TreeNode))
            return false;
        TreeNode castOther = (TreeNode) other;
        return new EqualsBuilder()
                .append(getName(), castOther.getName())
                .isEquals();
    }
    
  1. 请比较业务ID(这意味着您应该使用一些代表业务实体的唯一属性,例如名称)
  2. Hibernate Entity:请不要比较 Hibernate id
  3. Hibernate Entity:在访问其他对象字段时使用 getter 让 Hibernate 加载属性
  4. 如果您的类是子类,您可以调用 .appendSuper(super.equals(other))

    @Override
    public boolean equals(final Object other) {
        if (this == other)
            return true;
        if (!(other instanceof TreeNode))
            return false;
        TreeNode castOther = (TreeNode) other;
        return new EqualsBuilder()
                .append(getName(), castOther.getName())
                .isEquals();
    }
    

toString

  1. Please ensure that toString will not throw NullPointerException.
  2. You may call for .appendSuper(super.toString()) in case your class is subclass

    @Override
    public String toString() {
        return new ToStringBuilder(this)
                .append("Name", getName())
                .toString();
    }
    
  1. 请确保 toString 不会抛出 NullPointerException。
  2. 如果您的类是子类,您可以调用 .appendSuper(super.toString())

    @Override
    public String toString() {
        return new ToStringBuilder(this)
                .append("Name", getName())
                .toString();
    }
    

回答by Patrick

Knowing when overriding the hashCode and equals is not an easy task, there is another discussion where you have example and documentation link here What issues should be considered when overriding equals and hashCode in Java?

知道何时覆盖 hashCode 和 equals 不是一件容易的事,还有另一个讨论,您在此处提供示例和文档链接,在 Java 中覆盖 equals 和 hashCode 时应考虑哪些问题?

回答by Thomas Jung

As already mentioned you have to use a business key to implement equal and hashCode. Additionally you have to make your equals and hashCode implementation null-safeor add not null contraints(and invariant checks into your code) to ensure that the business key is never null.

如前所述,您必须使用业务密钥来实现 equal 和 hashCode。此外,您必须使您的 equals 和 hashCode 实现空安全或添加非空约束(以及对代码的不变检查)以确保业务键永远不会为空。

I suppose adding constraints is the right approach for your problem. Otherwise Role instances without names would be allowed and all these physical instances would be considered equal.

我想添加约束是解决您问题的正确方法。否则将允许没有名称的角色实例,并且所有这些物理实例将被视为相等。