重写持久化实体的hashCode()和equals()方法的正确方法是什么?
我有一个简单的类角色:
@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来构建更复杂的例子并不困难。
因此,我希望看到一些相关讨论的链接或听取您解决此问题的经验。 谢谢!
Bauer和King的书籍Java Persistence with Hibernate建议不要使用equals和hashCode的关键字段。 他们建议你应该选择对象的业务关键字段(如果没有人工密钥)并使用它们来测试相等性。 因此,在这种情况下,如果角色名称不是必需字段,您将找到必要的字段并将它们组合使用。 在你发布的代码的情况下,除了id之外你还拥有rolename,rolename就是我的目标。
这是第398页的引用:
我们认为基本上每个实体类都应该有一些业务键,即使它包含了类的所有属性(这适用于某些不可变类)。 业务键是用户的唯一标识特定记录的东西,而代理键是应用程序和数据库使用的。
业务键等式意味着equals()方法仅比较形成业务键的属性。 这是一个完美的解决方案,可以避免前面提到的所有问题。 唯一的缺点是,首先需要额外考虑识别正确的业务密钥。 无论如何都需要这种努力; 如果数据库必须通过约束检查确保数据完整性,则识别任何唯一键很重要。
我用来构造equals和hashcode方法的一种简单方法是创建一个toString方法,该方法返回’business key’字段的值,然后在equals()和hashCode()方法中使用它。 澄清:这是一种懒惰的方法,当我不关心性能时(例如,在rinky-dink内部webapps中),如果预期性能是一个问题,那么自己编写方法或使用IDE的代码生成工具。
我很抱歉因为批评而迟到,但没有其他人提到它, 这里有一个严重的缺陷 。 实际上可能是两个。
首先,其他人已经提到了如何处理null的可能性,但是一个好的hashcode()
和equals()
方法对的一个关键元素是它们必须服从契约,而你上面的代码不会这样做。
合同是equals()
返回true的对象必须返回相等的哈希码值 ,但在上面的类中,字段id和roleName是独立的。
这是一个致命缺陷的做法:您可以轻松地拥有两个具有相同roleName值但具有不同id值的对象。
实践是使用相同的字段来生成equals()方法使用的哈希码值,并且顺序相同。 下面是我的hashcode方法的替代品:
public int hashCode () { return ((roleName==null) ? 0 : roleName.hashcode()); }
注意:我不知道您使用id字段作为哈希码的意图,或者您对id字段的意图。 我从注释中看到它是生成的,但是它是外部生成的,因此所写的类无法履行合同。
如果由于某种原因你发现自己处于这样一种情况,即这个类完全由另一个类管理,忠实地为履行合同的roleNames生成“id”值,你就不会遇到function问题,但它仍然是不好的做法,或至少让人们称之为“代码味”。 除了类定义中没有任何东西可以保证类只能以这种方式使用之外, hashcode不是id,因此id不是hashcode 。
这并不意味着您不能使用保证等于 – 等于 – 角色名 – 值的标识符作为哈希码,但它们在概念上并不相同,所以至少,您应该有一个注释块解释你偏离预期的做法。
作为一个很好的一般规则,如果你发现自己必须这样做,你可能会犯一个设计错误。 不总是,但可能。 一个原因是什么? 人们并不总是阅读评论,所以即使你创建一个function完善的系统,随着时间的推移,有人会“滥用”你的课程并导致问题。
让类本身管理哈希码值的生成避免了这种情况。 并且您仍然可以保存并提供外部生成的ID,无论您使用它的目的是什么。
对象的业务键可能需要其父(或另一个一对一或多对一)关系。 在这些情况下,调用equals()或hashcode()可能会导致数据库命中。 除了性能之外,如果会话关闭将导致错误。 我大多放弃尝试使用业务键; 我使用主要ID并避免在地图和集合中使用未保存的实体。 到目前为止一直运作良好,但它可能取决于应用程序(注意通过父级联保存多个孩子)。 偶尔,我将使用一个单独的无意义的键字段,它是在构造函数中或由对象创建者自动生成的uuid。
知道何时覆盖hashCode和equals不是一件容易的事,还有另一个讨论,你在这里有示例和文档链接在Java中覆盖equals和hashCode时应该考虑哪些问题?
如前所述,您必须使用业务键来实现equal和hashCode。 此外,您必须使您的equals和hashCode实现为null安全或添加非null约束 (以及对代码的不变检查)以确保业务键永远不为null。
我想添加约束是解决问题的正确方法。 否则,将允许没有名称的角色实例,并且所有这些物理实例都将被视为相等。
请在下面找到如何使用Apache commons构建器创建hashCode,equals和toString方法的简单说明。
的hashCode
- 如果两个对象根据equals()方法相等,则它们必须具有相同的hashCode()值
- 两个不同的对象可能具有相同的hashCode()。
- 请使用唯一的Business ID进行hashCode创建(这意味着您应该使用一些代表业务实体的唯一属性,例如名称)
- Hibernate实体:请不要使用Hibernate id来创建hashCode
-
如果您的类是子类,您可以调用.appendSuper(super.hashCode())
@覆盖 public int hashCode(){ 返回新的HashCodeBuilder() .append(的getName()) .toHashCode(); }
等于
- 请比较商业ID(这意味着您应该使用代表商业实体的一些独特属性,例如,名称)
- Hibernate实体:请不要比较Hibernate id
- Hibernate Entity:在访问其他对象字段时使用getter让Hibernate加载属性
-
如果您的类是子类,您可以调用.appendSuper(super.equals(other))
@覆盖 public boolean equals(final Object other){ if(this == other) 返回true; if(!(TreeNode的其他实例)) 返回虚假; TreeNode castOther =(TreeNode)其他; 返回新的EqualsBuilder() .append(getName(),castOther.getName()) .isEquals(); }
的toString
- 请确保toString不会抛出NullPointerException。
-
如果您的类是子类,您可以调用.appendSuper(super.toString())
@覆盖 public String toString(){ 返回新的ToStringBuilder(this) .append(“Name”,getName()) 的ToString(); }
- EhCache Hibernate二级缓存maxBytesLocalHeap缓慢
- Hibernate / JPA是否考虑了transiant修饰符(而不是注释)
- 如何在spring MVC web应用程序中确认电子邮件addrees
- 缺少工件javax.transaction:jta:jar:1.0.1B(问题不同,因为您可能会看到分辨率不同)
- Spring + JPA +多个持久性单元:注入EntityManager
- Hibernate抛出HibernateQueryException:无法解析属性
- Hibernate:在同一个应用程序中使用两个不同的DataBase模式
- Hibernate命名策略更改表名
- 使用Hibernate持久收集界面