一、前言
Hibernate和Spring是两个著名的开源框架,越来越广泛地应用于J2EE应用程序的开发中。虽然它们各自所针对的目标不同,但是它们都有一个共同的特点:依赖性映射。Spring注重于在把对象返回到客户之前,帮助挑选出对象间的依赖性,这样以来大大减少了客户端的编码工作。Hibernate则专注于在把完整的对象模型返回到客户之前,挑选出对象模型描述的依赖性。当直接使用JDBC来把一个数据模型映射成一个对象模型时,我们通常需要编写大量的代码来建造一个对象模型。Hibernate可以帮助消除这种大部分的编码工作。
Hibernate 2.x提供了基本的表到对象的映射,正常的关联映射(包括一对一,一对多和多对多关系),多态映射,等等。Hibernate 3.x把这种技术又推进了一层-通过使用规则、过滤、子选择等等来加强映射的灵活性-它们提供了良好粒度的解释特性。
在本文中,我们将向你展示规则的各种不同特性是怎样帮助实现模型转换的。在Hibernate 3.x之前,规则属性只能出现在一个property元素中。虽然你还可以这样做,但是,Hibernate 3.x提供了一个规则属性或元素(其实,在涉及到规则使用时,这两者是等价的),它可以应用在许多元素中,包括discriminator,many-to-one,one-to-one,element,many-to-many,map-key,map-key-many-to-many以及property。这加强了对象-关系(O-R)映射的灵活性,并因此允许对更复杂的数据模型的良好粒度解释。
大致上有两种场合下,必须使用规则:
1. 需要规则计算结果的场所。与元素discriminator,element,map-key,map-key-many-to-many及property等一起使用的规则属于这一类型。
2. 借助于规则的帮助实现联结之目的。与元素many-to-one,one-to-one及many-to-many等一起使用的规则属于这一类型。
二、类型1:取得一个规则的计算结果
(一) Discriminator
在实际的数据模式中,经常有用一个表来描述另外一个表的情况。规则有助于在O-R映射中实现灵活的多态性。
在图1所示的例子中,共有两个表:Product和ProductRelease。每个产品记录都有一个ProductReleaseID,用于参照它相应的产品发行记录,包括产品发行名、类型、发行日期等。
在ProductRelease表中一个有趣的属性是SubProductAllowable,其值可以是0或1。值1意味着任何该产品的发行都可以有相应的子产品,而值0意味着不允许有子产品。例如,一些产品由多种子产品配置而成,而一些产品只能是其自身。
图2显示了一个从该数据模型解释出来的对象模型。其中,嵌套接口定义了getSubProducts和setSubProducts方法。类NestedProduct扩展了基类Product并实现了嵌套接口。一条产品数据记录是属于Product还是属于NestedProduct依赖于它相应的产品发行记录的SubProductAllowable的值。
图1.产品和产品发行数据模型 图2.产品和产品发行对象域模型 |
为了成功完成这个模型转化,我们使用了一个Hibernate3.x映射,如下:
<hibernate-mapping>
<class name="Product" discriminator-value="0" lazy="false">
<id name="id" type="long"/>
<discriminator formula="(select pr.SubProductAllowable from ProductRelease pr where pr.productReleaseID=
productReleaseID)" type="integer" />
<subclass name="NestedProduct" discriminator-value="1"/>
</class>
</hibernate-mapping>
如果规则表达式计算结果是0-也就是说,不允许有相应的子产品-那么该对象将是类Product。如果结果是1,该对象将是一个NestedProduct。在表1和表2中,对于Product表中的第一个记录(ProductID=10000001),初始化类将是一个NestedProduct,因为它通过SubProductAllowable=1参考引用一个ProductRelease记录。对于Product表中的第二个记录(ProductID=20000001),初始化类将是Product,因为它通过SubProductAllowable=0参考引用一个ProductRelease记录。
S/N | ProductReleaseID | SubProductAllowable | ... |
1 | 11 | 1 | ... |
2 | 601 | 0 | ... |
S/N | ProductID | ProductReleaseID | ... |
1 | 10000001 | 11 | ... |
2 | 20000001 | 601 | ... |
(二) Property
一个property元素中的规则允许对象属性包含某个派生值,如sum,average,max等等的结果,就象下面这样:
<property name="averagePrice" formula="(select avg(pc.price) from PriceCatalogue pc, SelectedItems si where si.priceRefID=pc.priceID)"/>
此外,一个规则,根据当前记录的某些属性值,还有助于检索来自于另一个表中的值。例如:
<property name="currencyName" formula="(select cur.name from currency cur where cur.id= currencyID)"/>
这有助于从当前货币表中检索一种货币的名称。很明显,这些直接的映射能消除很多的编码转化问题。
(三) map-key
规则还允许map-key取任何可能的值。在下列例中(图3),我们想要使Role_roleID成为对象模型的map-key(图4)。
图3.用户角色数据模式
图4.用户角色对象模型
在上面的数据模式中,User和Role通过一个称为User_has_Role的多对多的关系表被联系在一起。为了取得一个已赋给所有角色的用户,我们使用下列映射:
<hibernate-mapping>
<class name="User">
<id name="userID"/>
<map name="roles"
table="UserRole"/>
<key column="User_userID"/>
<map-key
formula="Role_RoleID"
type="string"/>
<many-to-many
column="Role_RoleID"
class="Role"/>
</map>
</class>
<class name="Role">
<id name="roleID"/>
</class>
</hibernate-mapping>
Role_RoleID用作多对多元素联结列列值。然而,Hibernate并不允许Role_RoleID出现在map-key和many-to-many的列属性中。但是借助于规则,Role_RoleID也可以用于map-key。
(四) 另外的情形:element,map-key-many-to-many以至更多
象property这样的元素,能够被赋于任何有效的规则表达式的计算值。
规则与map-key-many-to-many一起使用的情形类似于map-key。然而,map-key-many-to-many经常应用在三重关系表达时,这时一个map key自身就是一个参考对象,而不是一个参考属性。
然而,有些情况下并不支持规则的使用。一些数据库(例如Oracle7)并不支持嵌入的select语句(例如,一个select SQL语句嵌入在一个SQL语句的select部分),这时是不支持把规则用于运算结果的。因此,你需要先检查是否支持嵌入式select SQL语句。
既然Hibernate映射得到的SQL语句要把规则表达式作为它的select目标的一部分,充分了解你的数据库的SQL语法个性将有助于丰富你对于规则的使用,虽然它可能减弱代码的可移植性。
三、类型2:使用规则实现联合
(一) 多对一
在现实世界数据建模时,另外一个典型的场合是proprietary关系映射,这是一种不同于基本的一对一、一对多和多对多的关系映射。图5中的例子说明了,一个公司可能有多个联系人,但只有一个是缺省的联系人。有多个联系人的公司的情形就是一个典型的一对多关系。然而,为了识别出缺省的联系人,ContactPerson表使用了一个属性defaultFlag(这里1代表是,0代表不是)。
图5.用户角色数据模式
图6.用户角色对象模型
为把缺省的联系人关系解释成对象模型(图6),我们使用下列的映射:
<hibernate-mapping>
<class name="Company" table="Company">
<id name="id" />
<many-to-one
name="defaultContactPerson"
property-ref="defaultContactPerson">
<column name="id"/>
<formula>1</formula>
</many-to-one>
</class>
<class name="Person" >
<id name="id" />
<properties name="defaultContactPerson">
<property name="companyID" />
<property name="defaultFlag" />
</properties>
</class>
</hibernate-mapping>
在上面,我们把companyID和defaultFlag加以组合作为一个属性元素,并命名为defaultContactPerson,它构成了Person表的一个唯一键。Company类中的多对一元素与Person类中的defaultContactPerson属性元素相联合。结果,最后的SQL语句类似如下:
select c.id, p.id from Company c, Person p where p.companyID=c.id and p.defaultFlag=1
(二) 一对一
在Hibernate中,一对一主要用于两个表共享相同的primary键的情形。对于foreign键关系,我们通常使用多对一来代替。然而,借助于规则,一对一也能通过一个foreign键把表联接起来。例如,上面的多对一实例可以通过一对一方式作如下映射:
<hibernate-mapping>
<class name="Company" table="Company" >
<id name="id" />
<one-to-one name="defaultContactPerson"
property-ref="defaultContactPerson" >
<formula>id</formula>
<formula>1</formula>
</many-to-one>
</class>
<class name="Person" >
<id name="id" />
<properties name="defaultContactPerson">
<property name="companyID" />
<property name="defaultFlag" />
</properties>
</class>
</hibernate-mapping>
(三) 其它:多对多
规则可被用在一个多对多元素中来实现从关系表到实体表间的特殊联结,尽管这并不经常需要。
四、小结
本文中的示例展示了使用规则的大多数场所。当需要规则计算值时,规则表达式将出现在结果SQL的select语句中。当规则作于联合时,它将出现在结果SQL的where部分。而且,规则表达式能使用任何个性化的SQL,只要目标数据库支持它就行。我们的最终结论就是,灵活运用规则将大大帮助从数据模型到对象模型的具有良好粒度映射的建立,而这不需要任何编码工作。