我们的Person对象还缺少的是equals()方法和hashCode()方法的实现。既然这是一个持久性对象,我们并不想依赖于这两个方法的默认实现,因为默认实现并不能分辨代表数据库中同一行的两个不同实例。一种简单而又显然的实现方法是利用id字段来进行equal()方法的比较以及生成hashCode()方法的结果。
|
不幸的是,这个实现存在着问题。当我们首次创建Person对象时id的值为null,这意味着任何两个Person对象只要尚未保存,就将被认为是相等的。如果我们想创建一个Person对象并把它放到一个Set中,再创建一个完全不同的Person对象也把它放到同一个Set里面,事实上第二个Person对象并不能被加入。这是因为Set会断定所有未保存的对象都是相同的。
您可能会试图去实现一个使用id(只在已设置id的情况下)的equals()方法。毕竟,如果两个对象都没有被保存过,我们可以假定它们是不同的对象。这是因为在它们被保存到数据库的时候,它们会被赋予不同的主键。
|
这里有个隐含的问题。Java Collection框架在Collection的生命周期中需要基于不变字段的equals()和hashCode()方法。换句话来说,当一个对象处在Collection中的时候,不可以改变equals()和hashCode()的值。举个例子,下面这段程序:
Person p = new Person(); |
对set.contains(p)的第2次调用返回false,这是因为Set再也找不到p了。用专业术语来讲,就是Set丢失了这个对象!这是因为当对象在集合中时,我们改变了hashCode()的值。
当您想要创建一个将其他域对象保存在Set、Map或是List中的域对象时,这是一个问题。为了解决这个问题,您必须为所有对象提供一种equals()和hashCode()的实现,这种实现能够保证在它们在对象保存前后正确工作并且当对象在内存中时(返回值)不可变。Hibernate Reference Documentation (v. 3)提供了以下的建议:
“不要使用数据库标识符来实现相等性判断,而应该使用业务键(business key),这是一个唯一的、通常不改变的属性的组合体。当一个瞬态对象(transient object)被持久化的时候,数据库标识符会发生改变。当一个瞬态实例(常常与detached实例一起使用)保存在一个Set中时,哈希码的改变会破坏Set的约定。业务键的属性并不要求和数据库主键一样稳定,只要保证当对象在同一个Set中时它们的稳定性。”(Hibernate Reference Documentation v. 3.1.1)。
“我们推荐通过判断业务键相等性来实现equals()和hashCode()。业务键相等性意味着equals()方法只比较能够区分现实世界中实例的业务键(普通候选键)的属性。”(Hibernate Reference Documentation v. 3.1.1)。
换句话说,普通键用于equals()和hashCode(),而Hibernate生成的代理项键用于对象的id.这要求对于每个对象有一个相关的不可变的业务键。可是,并不是每个对象类型都有这样的一种键,这时候您可能会尝试使用会改变但不经常改变的字段。这和业务键不必与数据库主键一样稳定的思想相吻合。如果这种键在对象所在集合的生存期中不改变,那这就“足够好”了。这是一种危险的观点,因为这意味着您的应用程序可能不会崩溃,但是前提是没有人在特定的情况下更新了特定的字段。所以,应当有一种更好的解决方案,这种解决方案确实也存在。

