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

Rom*_*man 13 java hibernate jpa java-ee

我有一个简单的类角色:

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

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

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

public int hashCode () {
    return id; 
}
Run Code Online (Sandbox Code Playgroud)

但是当我创建新的Role对象时,它的id为null.这就是我在hashCode方法实现上遇到问题的原因.现在我可以简单地返回roleName.hashCode ()但是如果roleName不是必需的字段呢?我几乎可以肯定,通过返回其中一个字段的hashCode来构建更复杂的例子并不困难.

所以我希望看到相关讨论的一些链接或听取您解决此问题的经验.谢谢!

Nat*_*hes 13

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

这是第398页的引用:

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

业务键等式意味着equals()方法仅比较形成业务键的属性.这是一个完美的解决方案,可以避免前面提到的所有问题.唯一的缺点是,首先需要额外考虑识别正确的业务密钥.无论如何都需要这种努力; 如果数据库必须通过约束检查确保数据完整性,则识别任何唯一键很重要.

我用来构造equals和hashcode方法的一种简单方法是创建一个toString方法,该方法返回'business key'字段的值,然后在equals()和hashCode()方法中使用它.澄清:这是当我不关心性能偷懒的做法(例如,在rinky,丁克内部的webapps),如果业绩预期是一个问题,然后自己写的方法或使用IDE的代码生成工具.

  • 有关大量数据经验的旁注:在hashCode()或equals()中使用字符串连接(或任何非基本计算)将导致严重的,几乎无法跟踪的性能影响,尤其是.如果对象保存在大型集合中.不建议使用反射EqualsBuilders/HashCodeBuilders或使用toString().最好使用IDE生成的hashCode()方法,如Eclipse,它用于业务领域. (5认同)

CPe*_*ins 8

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

首先,其他人已经提到了如何处理null的可能性,但是一个好的hashcode()equals()方法对的一个关键要素是他们必须服从合同,而你上面的代码不会这样做.

合同是返回true的对象equals()必须返回相同的哈希码值,但在上面的类中,字段id和roleName是独立的.

这是一个致命缺陷的做法:您可以轻松地拥有两个具有相同roleName值但具有不同id值的对象.

实践是使用相同的字段来生成equals()方法使用的哈希码值,并且顺序相同.下面是我的hashcode方法的替代品:


public int hashCode () {
    return ((roleName==null) ? 0 : roleName.hashcode()); 
}
Run Code Online (Sandbox Code Playgroud)

注意:我不知道你使用id字段作为hashcode的意图,或者你对id字段的意图.我从注释中看到它是生成的,但是它是外部生成的,因此所写的类无法履行合同.

如果由于某种原因,你在这个类的情况发现自己完全被另一个忠实地生成它做履行合同的角色名"ID"的价值观管理,你不会有功能问题,但它仍然是不好的做法,或者至少让人们称之为"代码气味".除了类定义中没有任何东西可以保证类只能以这种方式使用之外,hashcode不是id,因此id不是hashcode.

这并不意味着你不能使用有保障的平等,为平等,角色名称值标识哈希码,但它们不是概念上是相同的,所以最起码,你应该有评论到块解释你偏离预期的做法.

作为一个很好的一般规则,如果你发现自己必须这样做,你可能会犯一个设计错误.并非总是,但可能.一个原因是什么?人们并不总是阅读评论,所以即使你创建一个功能完善的系统,随着时间的推移,有人会"滥用"你的课程并导致问题.

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