为什么当我有一个私有 id 字段时 Hibernate 要求我们实现 equals/hashcode 方法?

Shi*_*han 4 java hibernate jpa equals hashcode

首先,考虑片段,

public class Employee
{
    private Integer id;
    private String firstname;
    private String lastName;
    private String department;
 // public getters and setters here, i said PUBLIC
}
Run Code Online (Sandbox Code Playgroud)

我创建了 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));
Run Code Online (Sandbox Code Playgroud)

整个问题从这里开始 在实时应用程序中,这必须返回 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;
}
Run Code Online (Sandbox Code Playgroud)

现在,像往常一样:

        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);
Run Code Online (Sandbox Code Playgroud)

我正在阅读这篇优秀的文章不要让 Hibernate 窃取您的身份。但有一件事我未能完全理解。当 2 个员工对象 ID 相同时,上面讨论的整个问题及其解决方案以及链接的文章都在处理这些问题。

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

Vla*_*cea 5

如果实体定义了一个自然的业务键,那么您应该将它用于equalsand hashCode。自然标识符或业务键在所有实体状态转换中是一致的,因此hashCode当 JPA 实体状态发生变化时(例如从 New 到 Managed 到 Detached)不会改变。

在您的示例中,您使用的是assigned标识符,当您保留实体时该标识符不会更改。

但是,如果您没有自然标识符并且您有一个生成的标识符PRIMARY KEY(例如IDENTITY, SEQUENCE),那么您可以像这样实现 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
}
Run Code Online (Sandbox Code Playgroud)

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

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

这个版本的equalsandhashCode工作的原因是hashCode值不会从一个实体状态变为另一个,并且只有当它不是时才检查标识符null