应该如何在Hibernate中实现模型类的equals和hashcode?常见的陷阱是什么?对于大多数情况,默认实现是否足够好?使用商业密钥有什么意义吗?
在我看来,当考虑到懒惰的提取,id生成,代理等时,很难在任何情况下都能正常工作。
Hibernate对equals()
中何时/如何覆盖hashCode()
/ documentation有很好的描述
它的要点是,如果您的实体将成为Set
的一部分或者您将要分离/附加其实例,您只需要担心它。后者并不常见。前者通常最好通过以下方式处理:
equals()
/ hashCode()
- 例如属性的唯一组合,在对象(或至少是会话)生命周期内不会发生变化。equals()
/ hashCode()
,如果设置为对象,则为对象标识/ System.identityHashCode()
。这里的重要部分是,您需要在添加新实体并保持后重新加载您的Set;否则你可能最终会出现奇怪的行为(最终导致错误和/或数据损坏),因为您的实体可能被分配到与其当前hashCode()
不匹配的存储桶。我不认为接受的答案是准确的。
回答原来的问题:
对于大多数情况,默认实现是否足够好?
答案是肯定的,在大多数情况下都是如此。
你只需要覆盖equals()
和hashcode()
,如果实体将在Set
中使用(这是非常常见的)并且实体将与hibernate会话分离,并随后重新附加到hibernate会话(这是一种不常见的hibernate用法)。
接受的答案表明,如果任一条件为真,则需要覆盖这些方法。
当通过延迟加载加载实体时,它不是基类型的实例,而是由javassist生成的动态生成的子类型,因此对同一类类型的检查将失败,因此不要使用:
if (getClass() != that.getClass()) return false;
改为使用:
if (!(otherObject instanceof Unit)) return false;
这也是一个很好的做法,正如Implementing equals in Java Practices所解释的那样。
出于同样的原因,直接访问字段,可能无法工作并返回null,而不是基础值,因此不要在属性上使用比较,而是使用getter,因为它们可能会触发加载基础值。
当你使用equals
时,最好的hashCode
/ unique business key实现。
所有entity state transitions(瞬态,附加,分离,删除)的业务键应该是一致的,这就是为什么你不能依赖id来实现平等。
另一个选择是切换到使用由应用程序逻辑分配的UUID identifiers。这样,您可以将UUID用于equals
/ hashCode
,因为在刷新实体之前已分配id。
您甚至可以使用equals
和hashCode
的实体标识符,但这要求您始终返回相同的hashCode
值,以便确保实体hashCode值在所有实体状态转换中保持一致。看看this post for more on this topic。
是的,这很难。在我的项目中,equals和hashCode都依赖于对象的id。这个解决方案的问题是,如果对象尚未持久化,它们都不起作用,因为id是由数据库生成的。在我的情况下,这是可以容忍的,因为在几乎所有情况下,对象都会立即存在。除此之外,它工作得很好并且易于实现。
如果您碰巧覆盖equals
,请确保您履行合同: -
并覆盖hashCode
,因为它的合同依赖于equals
实现。
Joshua Bloch(Collection框架的设计者)强烈敦促遵循这些规则。
如果不遵守这些合同,会产生严重的意外影响。例如,List.contains(Object o)
可能会返回错误的boolean
值,因为一般合同未履行。
在Hibernate 5.2的文档中,它表示你可能根本不想实现hashCode和equals - 具体取决于你的情况。
通常,如果在数据库中它们相等(不实现hashCode和equals),则从同一会话加载的两个对象将是相等的。
如果您使用两个或更多会话,它会变得复杂。在这种情况下,两个对象的相等性取决于您的equals-method实现。
此外,如果您的equals-method正在比较仅在第一次持久化对象时生成的ID,则会遇到麻烦。当调用equals时,它们可能不存在。
这里有一篇非常好的文章:https://docs.jboss.org/hibernate/stable/core.old/reference/en/html/persistent-classes-equalshashcode.html
引用文章中的一条重要内容:
我们建议使用Business key equality实现equals()和hashCode()。业务键等式意味着equals()方法仅比较构成业务键的属性,这是一个在现实世界中识别我们实例的键(自然候选键):
简单来说
public class Cat {
...
public boolean equals(Object other) {
//Basic test / class cast
return this.catId==other.catId;
}
public int hashCode() {
int result;
return 3*this.catId; //any primenumber
}
}