文章作者 100test 发表时间 2008:04:07 17:07:54
来源 100Test.Com百考试题网
对象身份在虚拟机(VM)中和在数据库中的差异是问题滋生的温床。在虚拟机中,您并不会得到对象的ID,您只是简单地持有对象的直接引用。而在幕后,虚拟机确实给每个对象指派了一个8字节大小的ID,这个ID才是对象的真实引用。当您将对象持久存储到数据库中的时候,问题开始产生了。假定您创建了一个Person对象并将它存入数据库(我们可以叫它person1)。而您的其他某段代码从数据库中读取了这个Person对象的数据,并将它实例化为另一个新的Person对象(我们可以叫它Person2)。现在您的内存中有了两个映射到数据库中同一行的对象。一个对象引用只能指向它们的其中一个,可是我们需要一种方法来表示这两个对象实际上表示着同一个实体。这就是(在虚拟机中)引入对象身份的原因。
在Java语言中,对象身份是由每个对象都持有的equals()方法(以及相关的hashCode()方法)来定义的。无论两个对象是否为同一个实例,equals()方法都应该能够判别出它们是否表示同一个实体。hashCode()方法和equals()方法有关联是因为所有相等的对象都应该返回相同的hashCode.默认情况下,equals()方法仅仅比较对象引用。一个对象和它自身是相等的,而和其他任何实例都不相等。对于持久性对象来说,重写这两个方法,让代表着数据库中同一行的两个对象被视为相等是很重要的。而这对于Java中Collection(Set、Map和List)的正确工作更是尤为重要。
为了阐明实现equal()和hashCode()的不同途径,让我们考虑一个准备持久存储到数据库中的简单对象Person.
|
在这个例子中,我们遵循了同时持有id字段和version字段的最佳实践。Id字段保存了在数据库中作为主键使用的值,而version字段则是一个从0开始增长的增量,随着对象的每次更新而变化(这帮助我们避免并发更新的问题)。为了更清楚一些,让我们看看允许Hibernate把这个对象持久存储到数据库的Hibernate映射文件:
这里有个隐含的问题。Java Collection框架在Collection的生命周期中需要基于不变字段的equals()和hashCode()方法。换句话来说,当一个对象处在Collection中的时候,不可以改变equals()和hashCode()的值。举个例子,下面这段程序:
对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.这要求对于每个对象有一个相关的不可变的业务键。可是,并不是每个对象类型都有这样的一种键,这时候您可能会尝试使用会改变但不经常改变的字段。这和业务键不必与数据库主键一样稳定的思想相吻合。如果这种键在对象所在集合的生存期中不改变,那这就“足够好”了。这是一种危险的观点,因为这意味着您的应用程序可能不会崩溃,但是前提是没有人在特定的情况下更新了特定的字段。所以,应当有一种更好的解决方案,这种解决方案确实也存在。不要让Hibernate管理您的id. 试图创建和维护对象及数据库行的各自身份定义是目前为止所有讨论问题的根源。如果我们统一所有身份形式,这些问题都将不复存在。也就是说,作为以数据库为中心和以对象为中心的ID的替代品,我们应该创建一种通用的、特定于实体的ID来代表数据实体,这种ID应该在数据第一次输入的时候创建。无论这个唯一数据实体是保存在数据库中,是作为对象驻留在内存中,还是存储在其他格式的介质中,这个通用ID都应该可以识别它。通过使用数据实体第一次创建时指派的实体ID,我们可以安全地回到equals()和hashCode()的原始定义,它们只需使用这个id:
这个例子使用对象id作为equals()方法判断相等的标准,以及hashCode()返回哈希码的来源。这就简单了许多。但是,要让它正常工作,我们需要两样东西。首先,我们需要保证每个对象在被保存之前都有一个id值。在这个例子里,当id变量被声明的时候,它就被指派了一个值。其次,我们需要一种判断这个对象是新生成的还是之前保存过的的手段。在我们最早的例子中,Hibernate通过检查id字段是否为null来判断对象是否为新的。既然对象id永不为null,很显然这种方法不再有效。通过配置Hibernate,让它检查version字段,而不是id字段是否为null, 我们可以很容易地解决这个问题。version字段是一个更恰当的用来判断对象是否被保存过的指示符。
新版网站调整中,如需联系我们,请Email: evisa2011#gmail.com (#换@) ---- Copyright © 2000-2020.百考试题网 100Test.Com
|