【问题标题】:Why Hibernate requires us to implement equals/hashcode methods when I have a private id field?为什么当我有一个私有 id 字段时,Hibernate 要求我们实现 equals/hashcode 方法?
【发布时间】:2015-02-01 21:47:14
【问题描述】:

首先,考虑sn-p,

public class Employee
{
    private Integer id;
    private String firstname;
    private String lastName;
    private String department;
 // public getters and setters here, i said PUBLIC
}

我创建了 2 个具有相同 id 的对象,并且所有字段的其余部分也相同。

Employee e1 = new Employee();
Employee e2 = new Employee();

e1.setId(100);
e2.setId(100);

//Prints false in console
System.out.println(e1.equals(e2));

整个问题从这里开始 在实时应用程序中,这必须返回 true。

因此,每个人都知道存在解决方案(实现 equals() 和 hashcode())

public boolean equals(Object o) {
    if(o == null)
    {
        return false;
    }
    if (o == this)
    {
        return true;
    }
    if (getClass() != o.getClass())
    {
        return false;
    }

    Employee e = (Employee) o;
    return (this.getId() == e.getId());

}
@Override
public int hashCode()
{
    final int PRIME = 31;
    int result = 1;
    result = PRIME * result + getId();
    return result;
}

现在,像往常一样:

        Employee e1 = new Employee();
        Employee e2 = new Employee();

        e1.setId(100);
        e2.setId(100);

        //Prints 'true' now
        System.out.println(e1.equals(e2));

        Set<Employee> employees = new HashSet<Employee>();
        employees.add(e1);
        employees.add(e2);

        //Prints ofcourse one objects(which was a requirement)
        System.out.println(employees);

我正在阅读这篇出色的文章Don't Let Hibernate Steal Your Identity。但有一件事我没有完全理解。上面讨论的整个问题及其解决方案以及链接的文章正在处理 2 个 Employee 对象 ID 相同时的问题。

考虑一下,当我们有一个 id 字段private setter 时,id 字段由 generator 类生成hbm.xml 中提供。一旦我开始持久化 Employee 对象(我将无法更改 id),我发现不需要实现 equals 和 hashcode 方法。我确信我遗漏了一些东西,因为我的直觉告诉我们,当一个特定的概念在网络上旋转太多时,为了避免一些常见的错误,它一定总是摆在你面前?当我有一个用于 id 字段的私有设置器时,我还需要实现这两种方法吗?

【问题讨论】:

    标签: java hibernate jpa equals hashcode


    【解决方案1】:

    如果实体定义了自然业务密钥,则应将其用于equalshashCode。自然标识符或业务密钥在所有实体状态转换中都是一致的,因此 hashCode 在 JPA 实体状态更改时不会更改(例如,从 New 到 Managed 到 Detached)。

    在您的示例中,您使用的是 assigned 标识符,当您持久化实体时该标识符不会改变。

    但是,如果您没有自然标识符并且您有生成的 PRIMARY KEY(例如,IDENTITYSEQUENCE),那么您可以像这样实现 equals 和 hashCode:

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

    实体标识符可用于equals 和hashCode,但前提是hashCode 始终返回相同的值。这听起来像是一件可怕的事情,因为它违背了在HashSetHashMap 中使用多个存储桶的目的。

    但是,出于性能原因,您应该始终限制存储在集合中的实体数量。您永远不应该在 @OneToMany Set 中获取数千个实体,因为数据库端的性能损失比使用单个散列存储桶高多个数量级。

    这个版本的equalshashCode起作用的原因是hashCode value does not change from one entity state to another,只有在不是null时才会检查标识符。

    【讨论】:

    • 从概念上讲,你似乎真的是个男人。
    • 我从没听过这种说法,但我认为这是件好事,对吧?
    • 我喜欢你的回答。作者在那篇链接的文章(第2页)中提出了一点,“当您想要创建包含集合、映射或列表中的其他域对象的域对象时,这是一个问题。为此,您必须提供equals的实现() 和 hashCode() 为每个对象在保存对象之前和之后都有效,并且在对象在内存中时不会更改。”您能否详细说明这一行。他的意思是……当对象在内存中时?持久化在数据库或虚拟机内存中?
    • 您在您的网站上提到我们不能使用自动递增的数据库 ID 来比较对象,因为瞬态和附加对象版本不会彼此相等。但是为什么我会考虑比较瞬态对象和附加对象,我的意思是在哪个上下文/场景中?
    • 就像当你有一个父级时,它有一组子级,你将一个子级添加到该集中并持久化该子级。
    猜你喜欢
    • 2014-04-06
    • 2018-04-20
    • 2011-02-15
    • 2012-02-23
    • 1970-01-01
    • 1970-01-01
    • 2019-01-22
    相关资源
    最近更新 更多