我有一个简单的类角色:
@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;
}
}
现在我想覆盖它的方法equals和hashCode。我的第一个建议是:
public boolean equals (Object obj) {
if (obj instanceof Role) {
return ((Role)obj).getRoleName ().equals (roleName);
}
return false;
}
public int hashCode () {
return id;
}
但是当我创建新的Role对象时,它的id为null。这就是我在hashCode方法实现上遇到问题的原因。现在我可以简单地返回roleName.hashCode ()
但是如果roleName不是必需的字段呢?我几乎可以肯定,通过返回其中一个字段的hashCode来构建更复杂的例子并不困难。
所以我希望看到相关讨论的一些链接或听取您解决此问题的经验。谢谢!
答案 0 :(得分:13)
Bauer和King的书 Java Persistence with Hibernate 建议不要使用equals和hashCode的关键字段。他们建议您应该选择对象的业务关键字段(如果没有人工密钥)并使用它们来测试相等性。因此,在这种情况下,如果角色名称不是必填字段,您将找到必要的字段并将它们组合使用。如果您发布的代码除了id之外还有rolename,那么rolename就是我的目标。
以下是第398页的引用:
我们认为基本上每个实体类都应该有某些业务键,即使它包含了类的所有属性(这适用于某些不可变类)。业务键是用户的唯一标识特定记录的东西,而代理键是应用程序和数据库使用的。
业务键平等表示equals()方法仅比较构成业务键的属性。这是一个完美的解决方案,可以避免前面提到的所有问题。唯一的缺点是,首先需要额外考虑识别正确的业务密钥。无论如何都需要这种努力;如果数据库必须通过约束检查确保数据完整性,则识别任何唯一键很重要。
我用来构造equals和hashcode方法的一种简单方法是创建一个toString方法,该方法返回“业务键”字段的值,然后在equals()和hashCode()方法中使用它。澄清:这是一种懒惰的方法,当我不关心性能时(例如,在rinky-dink内部webapps中),如果预期性能是一个问题,那么自己编写方法或使用IDE的代码生成工具。
答案 1 :(得分:7)
我很抱歉因批评而迟到,但没有其他人提及它,这里有一个严重的缺陷。实际上可能是两个。
首先,其他人已经提到了如何处理null的可能性,但良好的hashcode()
和equals()
方法对的一个关键要素是它们必须遵守合同,并且上面的代码不是这样做。
合同是 equals()
返回true的对象必须返回相等的哈希码值,但在上面的类中,字段id和roleName是独立的。
这是一个致命缺陷的做法:你可以很容易地拥有两个具有相同roleName值但具有不同id值的对象。
实践是使用相同的字段来生成equals()方法使用的哈希码值,并且顺序相同。下面是我的哈希码方法的替代品:
public int hashCode () {
return ((roleName==null) ? 0 : roleName.hashcode());
}
注意:我不知道你使用id字段作为hashcode的意图,或者你想用id字段做什么。我从注释中看到它是生成的,但它是外部生成的,因此所写的类无法履行合同。
如果由于某种原因你发现自己处于这样一种情况,即这个类完全由另一个忠实地生成了履行合同的roleNames的“id”值,那么你就没有功能问题,但它仍然是不好的做法,或者至少有人称之为“代码味道”。除了类定义中没有任何东西可以保证类只能以这种方式使用之外,哈希码不是ids,所以ids不是哈希码。
这并不意味着您不能使用保证等于 - 等于 - 角色名称 - 值的标识符作为哈希码,但它们在概念上并不相同,所以非常至少,你应该有一个评论块来解释你偏离预期的做法。
作为一个很好的一般规则,如果你发现自己必须这样做,你可能会犯一个设计错误。并非总是,但可能。一个原因是什么?人们并不总是阅读评论,所以即使你创建一个功能完善的系统,随着时间的推移,有人会“滥用”你的课程并导致问题。
让类本身管理哈希码值的生成可以避免这种情况。无论出于何种目的,你仍然可以保存并提供外部生成的id。
答案 2 :(得分:2)
对象的业务键可能需要其父级(或另一种一对一或多对一)关系。在这些情况下,调用equals()或hashcode()可能会导致数据库命中。除了性能之外,如果会话关闭将导致错误。我大多放弃尝试使用业务键;我使用主id并避免在地图和集合中使用未保存的实体。到目前为止一直运作良好,但它可能取决于应用程序(注意通过父级联保存多个孩子)。偶尔,我会使用一个单独的无意义的关键字段,它是在构造函数中或由对象创建者自动生成的。
答案 3 :(得分:1)
知道何时覆盖hashCode和equals不是一件容易的事, 还有另一个讨论,你在这里有示例和文档链接What issues should be considered when overriding equals and hashCode in Java?
答案 4 :(得分:1)
如前所述,您必须使用业务键来实现equal和hashCode。此外,您必须使您的equals和hashCode实现 null-safe 或添加 not null contraints (以及对代码进行不变检查)以确保业务密钥永远不为null。
我认为添加约束是解决问题的正确方法。否则,将允许没有名称的角色实例,并且所有这些物理实例都将被视为相等。
答案 5 :(得分:1)
请在下面找到如何使用Apache commons构建器创建hashCode,equals和toString方法的简单说明。
如果您的类是子类,您可以调用.appendSuper(super.hashCode())
@Override public int hashCode() { return new HashCodeBuilder() .append(getName()) .toHashCode(); }
如果您的类是子类,您可以调用.appendSuper(super.equals(other))
@Override public boolean equals(final Object other) { if (this == other) return true; if (!(other instanceof TreeNode)) return false; TreeNode castOther = (TreeNode) other; return new EqualsBuilder() .append(getName(), castOther.getName()) .isEquals(); }
如果您的类是子类,您可以调用.appendSuper(super.toString())
@Override public String toString() { return new ToStringBuilder(this) .append("Name", getName()) .toString(); }
答案 6 :(得分:0)
我在项目中使用了Lombok,这是适用于JPA实体的规则的汇总,我将equals()
和hashCode()
的特定实体移到了顶部
创建时,请确保以下内容
EqualsHashCode仅应在“业务键”上完成
复合密钥通常是业务密钥。因此它们不得为EqualsHashCode.Exclude
EqualsHashCode。排除“代理密钥” /“技术ID”,例如生成的ID,版本,审核字段,由@PrePersist或@PreUpdate更新的值
ManyToOne必须具有EqualsHashCode.Exclude和ToString.Exclude以防止堆栈溢出
ManyToOne必须具有对应的EqualsHashCode.Include和ToString.Include才能获取引用对象的ID字段。该方法必须带有ID后缀,并且不能以get
开头@Entity
类名必须为单数。 @Repository
类名将是复数。@Entity
必须具有@Data
@Entity
必须为Serializable
@Entity
必须具有@NoArgsConstructor
@OneToMany
必须具有cascadeType = Cascade.ALL @OneToMany
必须具有mappingBy @OneToMany
必须具有orphanRemoval = true @OneToMany
必须是集合使用两个等效的纸牌的类比,它们被弄脏并混在一起。收集将是套牌,其中应包含具有唯一可识别值的卡。
公司的钥匙是西服,甚至是卡号。这些唯一地标识卡的价值。
仅在卡片表面上的污垢图案不相关。因此,它不是业务密钥的一部分。
卡本身的物理存在也不是业务密钥的一部分。这类似于为卡生成的私钥。这也称为代理密钥或技术ID。