105

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

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

4

8 回答 8

81

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

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

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

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

要回答原始问题:

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

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

您只需要覆盖equals()并且hashcode()如果实体将用于Set(这是非常常见的)并且实体将从休眠会话中分离并随后重新连接到休眠会话(这是休眠的不常见用法)。

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

于 2012-10-29T03:38:23.170 回答
19

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

@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。

您甚至可以将实体标识符用于equalsand hashCode,但这要求您始终返回相同的 [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
}
于 2014-06-27T20:34:46.937 回答
13

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

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

改为使用:

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

这也是一个很好的做法,正如在 Java 实践中实现 equals 中所解释的那样。

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

于 2013-01-10T09:48:51.087 回答
6

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

于 2009-10-28T17:19:24.133 回答
1

在 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 时,它们可能还不存在。

于 2016-10-20T13:46:58.627 回答
0

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

  • 对称
  • 反光的
  • 及物
  • 持续的
  • 非空

和 override hashCode,因为它的合同依赖于equals实施。

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

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

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

于 2016-03-02T03:17:50.097 回答
0

这里有一篇很好的文章: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 
}

}
于 2016-01-02T12:50:47.693 回答