4.3. 实现 equals() 和 hashCode() 方法:
如果你有如下需求,你必须重载 equals()
和 hashCode()
方法:
想把持久类的实例放入
Set
中(当表示多值关联时,推荐这么做),而且想重用脱管实例
Hibernate 保证,仅在特定会话范围内,持久化标识(数据库的行)和 Java 标识是等价的。因此,一旦我们混合了从不同会话中获取的实例,如果希望 Set
有明确的语义,就必须实现 equals()
和 hashCode()
。
实现 equals()
/hashCode()
最显而易见的方法是比较两个对象 标识符的值。如果值相同,则两个对象对应于数据库的同一行,因此它们是相等的(如果都被添加到 Set
,则在 Set
中只有一个元素)。不幸的是,对生成的标识不能 使用这种方法。Hibernate 仅对那些持久化对象赋标识值,一个新创建的实例将不会有任何标识值。此外, 如果一个实例没有被保存(unsaved),并且它当前正在一个 Set
中,保存它将会给这个对象赋一个标识值。如果 equals()
和 hashCode()
是基于标识值 实现的,则其哈希码将会改变,这违反了 Set
的契约。建议去 Hibernate 的站点阅读关于这个问题的全部讨论。注意,这不是 Hibernate 的问题,而是一般的 Java 对象标识和 Java 对象等价的语义问题。
我们建议使用业务键值相等(Business key equality)来实现 equals()
和 hashCode()
。业务键值相等的意思是,equals()
方法仅仅比较形成业务键的属性,它能在现实世界里标识我们的实例(是一个自然的候选码)。
public class Cat { ... public boolean equals(Object other) { if (this == other) return true; if ( !(other instanceof Cat) ) return false; final Cat cat = (Cat) other; if ( !cat.getLitterId().equals( getLitterId() ) ) return false; if ( !cat.getMother().equals( getMother() ) ) return false; return true; } public int hashCode() { int result; result = getMother().hashCode(); result = 29 * result + getLitterId(); return result; } }
A business key does not have to be as solid as a database primary key candidate (see 第 12.1.3 节 “关注对象标识(Considering object identity)”). Immutable or unique properties are usually good candidates for a business key.