|
现在我们有了一个简单而高效的方法来创建域对象。它们扩展了AbstractPersistentObject,该父类能在它们第一次创建时自动赋予一个id,并且恰当地实现了equals()和hashCode()。域对象也得到了一个toString()方法的合理默认实现,这个方法可以有选择地被重写。如果这是一个查询例子的测试对象或者示例对象,id可以被修改或者被设为null.否则它是不应当被改变的。如果因为某些原因我们需要创建一个扩展其他类的域对象,这个对象就应当实现PersistentObject接口而不是扩展抽象类。
Person类现在就简单多了:
|
从上一个例子开始Hibernate映射文件就不会再改变了。我们不想麻烦Hibernate去了解抽象父类,我们只要保证每个PersistentObject映射文件包含一个id项(和一个“被指派的”生成器)和一个带有unsaved-value="null"属性的version标签。机敏的读者可能已经注意到,每当一个持久性对象被实例化的时候,它的id得到了指派。这意味着当Hibernate在内存中创建一个已保存对象的实例时,虽然这个对象是已经存在并从数据库中读取的,它也会得到一个新的id.这说好了。然后Hibernate会接着调用对象的setId()方法,用保存的id来替换新分配的id.额外的id生成并不是什么问题,因为id生成算法是廉价的(也就是说,它并不牵扯到数据库)。
到现在为止一切都很好,但是我们遗漏了一个重要的细节:如何实现IdGenerator.createId()。我们可以为理想中的键生成(key-generation)算法定义一些标准:
键可以不牵扯到数据库而很廉价地生成。
即使跨越不同的虚拟机和不同机器,键也要保证唯一性。
如果可能,键可以由其他程序、编程语言和数据库生成,但是至少要能与它们兼容。
我们所需的是通用唯一标识符(universally unique identifier,UUID)。UUID由16个字节(128位)的数字组成,遵守标准格式。UUID的String版本看起来类似如下:
2cdb8cee-9134-453f-9d7a-14c0ae8184c6
里面的字符是简单的字节16进制表示,横线把数字的不同部分分隔开来。这种格式简单而且易于处理,只是36个字符有点长了。因为横线总是被安置在相同的位置,所以可以把它们去掉,从而把字符的数目减少到32个。为了更为简洁地表示,可以创建一个byte[16]的数组或是两个8字节大小的long来保存这些数字。如果您使用的是Java 1.5或更高版本,可以直接使用UUID类,虽然这不是它在内存中最简洁的格式。有关更多信息,请参阅Wikipedia UUID条目和JavaDoc UUID类条目。
UUID生成算法有多种实现。既然最终UUID是一种标准格式,我们在IdGenerator类中采用哪一种实现都没有关系。既然无论采用什么算法每个id都会被保证唯一,我们甚至可以在任何时候改变算法的实现或是混合匹配不同的实现。如果您使用的是Java 1.5或更高版本,最方便的实现是java.util.UUID类:
public class IdGenerator { |
对不使用Java 1.5或更高版本的人来说,至少有两种扩展库实现了UUID并且与1.5之前的Java版本兼容:Apache Commons ID项目和Java UUID Generator (JUG)项目。它们在Apache License之下都是可用的(在LGPL之下JUG也是可用的)。
这是使用JUG库实现IdGenerator的例子:
|
Hibernate中内置的UUID生成器算法又如何呢?这是获得对象身份的UUID的适当途径吗?如果您想让对象身份独立于对象持久性,这就不是一个好方法。虽然Hibernate确实提供了生成UUID的选项,但这样的话我们又回到了最早的那个问题上:对象ID的获得并不在它们被创建的时候,而是在它们被保存的时候。
使用UUID作为数据库主键的最大障碍是它们在数据库中(而不是在内存中)的大小,在数据库中索引和外键的复合会促使主键大小的增加。您必须在不同情况下使用不同的表示方法。使用String表示,数据库的主键大小将会是32或36字节。数字也可以直接以字节存储,这样大小就减少一半,但是如果直接查询数据库,标识符将变得难以理解。这些方法对您的项目是否可行取决于您的需求。
如果数据库不接受UUID作为主键,您可以考虑使用数据库序列。但总是应该在新对象创建的时候被指派一个ID而不是让Hibernate管理ID.在这种情况下,创建新域对象的业务对象可以调用一个使用数据访问对象(DAO)从数据库序列中检索id的服务。如果使用一个Long数据类型来表示对象id,一个单独的数据库序列(以及服务方法)对您的域对象来说就已经足够了。
结束语
当对象持久存储到数据库中时,对象身份总是很难被恰当地实现。尽管如此,问题其实完全在于,对象在保存之前允许对象没有id就存在。我们可以通过从诸如Hibernate这样的对象关系映射框架中获得指派对象ID的职责来解决这个问题。一旦对象被实例化,它就应该被指派一个ID.这使对象身份变得简单而不易出错,也减少了域模型中需要的代码量。

