ChatGPT解决这个技术问题 Extra ChatGPT

使用JPA和Hibernate时equals和hashcode应该如何实现

模型类的equals和hashcode应该如何在Hibernate中实现?常见的陷阱有哪些?对于大多数情况,默认实现是否足够好?使用业务密钥有什么意义吗?

在我看来,当考虑到延迟获取、id 生成、代理等时,很难让它在每种情况下都能正常工作。

另请参阅 stackoverflow.com/a/39827962/548473(spring-data-jpa 实现)

f
fabien7474

Hibernate 对何时/如何覆盖 documentation 中的 equals() / hashCode() 有一个很好的详细描述

它的要点是,如果您的实体将成为 Set 的一部分,或者您要分离/附加其实例,您只需要担心它。后者并不常见。前者通常最好通过以下方式处理:

基于业务键的 equals() / hashCode() - 例如,在对象(或至少,会话)生命周期内不会更改的属性的唯一组合。如果以上是不可能的,则在主键上设置 equals() / hashCode() ,否则设置对象身份 / System.identityHashCode() 。这里的重要部分是您需要在将新实体添加到 Set 并持久化后重新加载;否则您可能会遇到奇怪的行为(最终导致错误和/或数据损坏),因为您的实体可能被分配到与其当前 hashCode() 不匹配的存储桶。


当您说“重新加载”@ChssPly76 时,您的意思是执行 refresh()?遵守 Set 合同的实体如何最终进入错误的存储桶(假设您有足够好的哈希码实现)。
刷新集合或重新加载整个(所有者)实体,是的。就错误的存储桶而言:a)您添加了要设置的新实体,它的 id 尚未设置,因此您使用 identityHashCode 将您的实体放在存储桶 #1 中。 b)您的实体(在集合内)是持久的,它现在确实有一个 id,因此您正在使用基于该 id 的 hashCode() 。它与上面不同,将您的实体放入存储桶 #2。现在,假设您在其他地方持有对该实体的引用,请尝试调用 Set.contains(entity),您将返回 false。 get() / put() / 等也是如此...
有道理,但我自己从未使用过 identityHashCode 虽然我看到它在 Hibernate 源中使用,就像在他们的 ResultTransformers 中一样
使用 Hibernate 时,您也可能遇到 this problem,我仍然没有找到解决方案。
@ChssPly76 由于确定两个对象是否相等的业务规则,我需要将我的 equals/hashcode 方法基于对象生命周期内可能发生变化的属性。这真的很重要吗?如果是这样,我该如何解决?
P
Phil

我不认为接受的答案是准确的。

要回答原始问题:

对于大多数情况,默认实现是否足够好?

答案是肯定的,在大多数情况下是这样。

如果实体将在 Set 中使用(这很常见)AND 实体将从中分离并随后重新附加到,则您只需要覆盖 equals()hashcode() ,休眠会话(这是休眠的不常见用法)。

接受的答案表明,如果任一条件为真,则需要覆盖这些方法。


这与我的观察一致,是时候找出 why
“如果实体将在 Set 中使用,则只需要覆盖 equals() 和 hashcode()”如果某些字段标识一个对象就足够了,因此您不想依赖 Object.equals() 来标识对象。
V
Vlad Mihalcea

最好的 equalshashCode 实现是当您使用唯一的业务密钥或自然标识符时,如下所示:

@Entity
public class Company {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
 
    @Column(unique = true, updatable = false)
    private String name;
 
    @Override
    public int hashCode() {
        HashCodeBuilder hcb = new HashCodeBuilder();
        hcb.append(name);
        return hcb.toHashCode();
    }
 
    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (!(obj instanceof Company)) {
            return false;
        }
        Company that = (Company) obj;
        EqualsBuilder eb = new EqualsBuilder();
        eb.append(name, that.name);
        return eb.isEquals();
    }
}

业务密钥应该在所有实体状态转换(瞬态、附加、分离、删除)中保持一致,这就是为什么你不能依赖 id 来实现平等。

另一种选择是切换到使用由应用程序逻辑分配的 UUID 标识符。这样,您可以将 UUID 用于 equals/hashCode,因为在刷新实体之前分配了 ID。

您甚至可以为 equalshashCode 使用实体标识符,但这要求您始终返回相同的 [hashCode 值,以确保实体 hashCode 值在所有实体状态转换中保持一致,如下所示:

@Entity(name = "Post")
@Table(name = "post")
public class Post implements Identifiable<Long> {
 
    @Id
    @GeneratedValue
    private Long id;
 
    private String title;
 
    public Post() {}
 
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
 
        if (!(o instanceof Post))
            return false;
 
        Post other = (Post) o;
 
        return id != null &&
               id.equals(other.getId());
    }
 
    @Override
    public int hashCode() {
        return getClass().hashCode();
    }
  
    //Getters and setters omitted for brevity
}

+1 用于 uuid 方法。将其放入 BaseEntity 中,不再考虑该问题。它在 db 端需要一些空间,但你最好为舒适付出这个代价:)
s
stivlo

当通过延迟加载加载实体时,它不是基类型的实例,而是javassist生成的动态生成的子类型,因此对同一类类型的检查会失败,所以不要使用:

if (getClass() != that.getClass()) return false;

改为使用:

if (!(otherObject instanceof Unit)) return false;

这也是一个很好的做法,如 Implementing equals in Java Practices 中所述。

出于同样的原因,直接访问字段可能不起作用并返回 null,而不是基础值,因此不要对属性使用比较,而是使用 getter,因为它们可能会触发加载基础值。


如果您正在比较具体类的对象,这在我的情况下不起作用。我正在比较超类的对象,在这种情况下,这段代码对我有用:obj1.getClass().isInstance(obj2)
C
Carlos

是的,这很难。在我的项目中,equals 和 hashCode 都依赖于对象的 id。这个解决方案的问题是,如果对象还没有被持久化,它们都不起作用,因为 id 是由数据库生成的。就我而言,这是可以容忍的,因为在几乎所有情况下,对象都会立即持久化。除此之外,它工作得很好并且很容易实现。


我认为我们所做的是在没有生成 id 的情况下使用对象标识
这里的问题是,如果你持久化对象,你的哈希码就会改变。如果对象已经是基于哈希的数据结构的一部分,则可能会产生很大的不利结果。因此,如果您最终使用对象标识,则最好继续使用 obj id 直到对象完全释放(或从任何基于散列的结构中删除对象,持久化,然后将其重新添加)。就个人而言,我认为最好不要使用 id,并将散列基于对象的不可变属性。
N
Nina

在 Hibernate 5.2 的文档中,它说您可能根本不想实现 hashCode 和 equals - 取决于您的情况。

https://docs.jboss.org/hibernate/orm/5.2/userguide/html_single/Hibernate_User_Guide.html#mapping-model-pojo-equalshashcode

一般来说,从同一个会话加载的两个对象如果在数据库中相等,则它们将相等(不实现 hashCode 和 equals)。

如果您使用两个或更多会话,它会变得复杂。在这种情况下,两个对象的相等性取决于您的 equals 方法实现。

此外,如果您的 equals 方法正在比较仅在第一次持久化对象时生成的 ID,您将遇到麻烦。当调用 equals 时,它们可能还不存在。


R
Ravi Shekhar

这里有一篇很好的文章:https://docs.jboss.org/hibernate/stable/core.old/reference/en/html/persistent-classes-equalshashcode.html

引用文章中的一句重要的话:

我们建议使用业务键相等来实现 equals() 和 hashCode()。业务键相等意味着 equals() 方法仅比较形成业务键的属性,该键将识别我们在现实世界中的实例(自然候选键):

简单来说

public class Cat {

...
public boolean equals(Object other) {
    //Basic test / class cast
    return this.catId==other.catId;
}

public int hashCode() {
    int result;

    return 3*this.catId; //any primenumber 
}

}

A
Awan Biru

如果您碰巧覆盖了 equals,请确保您履行其合同:-

对称

反光的

及物

持续的

非空

并覆盖 hashCode,因为它的合同依赖于 equals 实现。

Joshua Bloch(Collection 框架的设计者)强烈要求遵守这些规则。

项目 9:当你覆盖 equals 时,总是覆盖 hashCode

如果您不遵守这些合同,则会产生严重的意外影响。例如,List#contains(Object o) 可能会返回错误的 boolean 值,因为一般合同未履行。