映射
.1 简介
关联映射是最难对付的问题。在此部分中我们将逐一地处理各种情况。从单向映射开始,然后是双向映射。我们将在全部例子中使用Person及Address说明。
我们将明确表明,这些关联是否映射向连接表,以及是否多的关系。
在传统的数据建模中,允许空值的外键不被认为是好的实践。因此我们所有的例子都使用非空的外键。这并非Hiberante的要求,并且如果你删除空值约束,映射依旧可以工作。
.2 单向关联
.2.1 多对一关联
单向的多对一关联是最普遍的单向关联。
<class name="Person">
<id name="id" column="personId">
<generator class="native"/>
</id>
<many-to-one name="address"
column="addressId"
not-null="true"/>
</class>
<class name="Address">
<id name="id" column="addressId">
<generator class="native"/>
</id>
</class>
create table Person ( personId bigint not null primary key, addressId bigint not null )
create table Address ( addressId bigint not null primary key )
注解.2.1:单向的多对一关联,“一”方可以不用写“one-to-many”,只需要在“多”方写“many-to-one”。从数据库的角度,“一”方没有外键,而“多”方却有一外键与“一”方相联。因此,有外键的表需要写“many-to- one”,并且通过在<many-to-one>中的column属性指向主表的主键,column的属性值可以与主表的主键名不一样。另外,由于“一”方没有外键,因此它对外界一无所知,从所映射的Java代码中不能检索到其他表的信息。但我们可以通过join的方式来与其他表连接。
.2.2 一对一关联
基于外建上的单向一对一关联(与上面所述的多对一关联)几乎是一样的。唯一的区别是字段上多了一个unique约束。
<class name="Person">
<id name="id" column="personId">
<generator class="native"/>
</id>
<many-to-one name="address"
column="addressId"
unique="true"
not-null="true"/>
</class>
<class name="Address">
<id name="id" column="addressId">
<generator class="native"/>
</id>
</class>
create table Person ( personId bigint not null primary key, addressId bigint not null unique )
create table Address ( addressId bigint not null primary key )
基于主键上的单向的一对一关联通常使用一个特殊的id生成器。(请注意在此例中我们已经反转了关联的方向。)
<class name="Person">
<id name="id" column="personId">
<generator class="native"/>
</id>
</class>
<class name="Address">
<id name="id" column="addressId">
<generator class="foreign">
<param name="property">person</param>
</generator>
</id>
<one-to-one name="person" constrained="true"/>
</class>
(译注:原文中Address的id的column=”personId”,应为”adderssId”)
create table Person ( personId bigint not null primary key )
create table Address ( personId bigint not null primary key )
注解.2.2:意味着在Address的主键addressId上有一指向person表的约束,这个约束通过名为person的属性进行关联。
.2.3 一对多关联
在外键上的单向一对多关系很不常见,因此确实不推荐使用。
<class name="Person">
<id name="id" column="personId">
<generator class="native"/>
</id>
<set name="addresses">
<key column="personId" ///Address表的/外键标识/ 关联本表(person表)主键
not-null="true"/>
<one-to-many class="Address"/>//与本表关联的Address类
</set>
</class>
<class name="Address">
<id name="id" column="addressId">
<generator class="native"/>
</id>
</class>
create table Person ( personId bigint not null primary key )
create table Address ( addressId bigint not null primary key, personId bigint not null )
我们认为最好使用一个连接表来应付这种情况。
注解.2.3:注意此例与前面的几个例子不同。
前面几个例子都是Person作为多方,Address作为一方,是多对一的关系,含义为多个人拥有同一个地址,如一家人住在一起。每个 Person只有一个地址,但Address则拥有多个Person。在Address的Java代码中,可能有Set<Person> Person = new HashSet<Person>()的定义。但由于此节只讨论单向的关联,即从Person到Address的关联,不需要从Address 中访问Person,因此,Address的代码可以不含有Set<Person> Person = new HashSet<Person>()的代码,在Hibernate映射文件中也不需要有<set>的定义。
而最后的一个例子则为Person作为一方,Address作为多方,是一对多的关系,含义为一个人有多个地址,如同时拥有别墅和宿舍。作为单向关联,我们需要从Person访问Address,因此,必须有<set>的定义。从含义来讲,这种情况是常见的。因此,在单向关联中,如果存在多对一的关联,而我们又需要从多方关联到一方,如本例,完全可以这样定义。
Hibernate Team不推荐此用法,只在于推荐将此语法转化为下面3.3.1节中谈到的语法,而不在于否定单向关联中从多方到一方的使用。
.3 与连接表的单向关联
.3.1 一对多关联
在连接表上的一对多关联更值得推荐。请注意,通过设定unique=”true”,我们将多对多关联变为一对多关联。
<class name="Person">
<id name="id" column="personId">
<generator class="native"/>
</id>
<set name="addresses" table="PersonAddress"> //中间表PersonAddress
<key column="personId"/>
<many-to-many column="addressId"
unique="true"
//但通过设定unique=”true”后,确实转化为一对多的关联。此节创建了三个表,设定了unique=”true”之后, PersonAddress表中addressId成为唯一值的主键(主键?),即每个地址只对应于一个人,不允许出现两个人同时拥有同一个地址的情况,但一个人可以拥有两个以上的地址。
class="Address"/>
</set>
</class>
<class name="Address">
<id name="id" column="addressId">
<generator class="native"/>
</id>
</class>
create table Person ( personId bigint not null primary key )
create table PersonAddress ( personId not null, addressId bigint not null primary key )
create table Address ( addressId bigint not null primary key )
注解.3.1: 从表面上看,这种形式是典型的多对多关联,即多对多关系通过一个中间表转换为各自对中间表的一对多关系,即一个人可以同时有多个地址,而一个地址又同时有多个人居住。但通过设定unique=”true”后,确实转化为一对多的关联。此节创建了三个表,设定了unique=”true”之后, PersonAddress表中addressId成为唯一值的主键,即每个地址只对应于一个人,不允许出现两个人同时拥有同一个地址的情况,但一个人可以拥有两个以上的地址。这种关联,是从Person到Address的一对多的关联。因此,与3.2.3的环境相同。
注意1。这种方式使得中间表具有仅由一个单一字段构成的主键,与传统多对多关联中的中间表一般由两个以上的字段组成复合主键不同。如果我们有这种情况出现,可以好好地利用这种方式。但应注意,此种方法已经背离了通过多表来表现多对多的关系的初衷,而是一种实实在在的一对多关联。.
注意2。另外一个值得注意的地方,是此种方法未使用一个代理键作为主键,而是使用了一个在中间表中有实际意义的外键作为主键,尽管此键在主表中只不过是一个代理键。尽管前面的例子中,都出现是外键关联主表主键的情况,但外键均不是主键。而在此例中,外键(addressId)成为中间表的主键了。
3.2 多对一关联(总觉join是在连接表多的一方定义!呵呵)
当关联为可选时,在连接表上的单向的多对一关联非常普遍。
<class name="Person">//多的一方
<id name="id" column="personId">
<generator class="native"/>
</id>
<join table="PersonAddress"
optional="true">
<key column="personId" unique="true"/>
<many-to-one name="address" //many2one有name属性,此address是Person类中的属性!
column="addressId"
not-null="true"/>
</join>
</class>
<class name="Address">
<id name="id" column="addressId">
<generator class="native"/>
</id>
</class>
create table Person ( personId bigint not null primary key )
create table PersonAddress ( personId bigint not null primary key, addressId bigint not null )
create table Address ( addressId bigint not null primary key )
注解.3.2: 这种方式更接近于数据库而不是面向对象的视角,是从中间表的角度来定义的,而且中间表PersonAddress的主键已经转移到personId上了。
.3.3 一对一关联
在连接表上的单向的一对一关联非常少见,但也是可以的。
<class name="Person">
<id name="id" column="personId">
<generator class="native"/>
</id>
<join table="PersonAddress"
optional="true">
<key column="personId"
unique="true"/>
<many-to-one name="address"
column="addressId"
not-null="true"
unique="true"/>
</join>
</class>
<class name="Address">
<id name="id" column="addressId">
<generator class="native"/>
</id>
</class>
create table Person ( personId bigint not null primary key )
create table PersonAddress ( personId bigint not null primary key, addressId bigint not null unique )
create table Address ( addressId bigint not null primary key )
.3.4 多对多关联
最后,是单向的多对多关联。
<class name="Person">
<id name="id" column="personId">
<generator class="native"/>
</id>
<set name="addresses" table="PersonAddress">
<key column="personId"/>
<many-to-many column="addressId"
class="Address"/>
</set>
</class>
<class name="Address">
<id name="id" column="addressId">
<generator class="native"/>
</id>
</class>
create table Person ( personId bigint not null primary key )
create table PersonAddress ( personId bigint not null, addressId bigint not null, primary key (personId, addressId) )
create table Address ( addressId bigint not null primary key )
注解.3.4: 这才是真正意义上的多对多关联。
.4 双向关联
.4.1 一对多 / 多对一
双向的多对一关联是关联中最常见的。(这是标准的父/子关系。)
<class name="Person">
<id name="id" column="personId">
<generator class="native"/>
</id>
<many-to-one name="address"
column="addressId"
not-null="true"/>
</class>
<class name="Address">
<id name="id" column="addressId">
<generator class="native"/>
</id>
<set name="people" inverse="true">
<key column="addressId"/>
<one-to-many class="Person"/>
</set>
</class>
create table Person ( personId bigint not null primary key, addressId bigint not null )
create table Address ( addressId bigint not null primary key )
如果你使用List(或者其它索引集合),你需要将外键所在的关键字段设为not null,并且让Hibernate从集合一端管理关联,以维护每位元素的索引(通过设定update=”false”及insert=”false”来使另一方反转):
<class name="Person">
<id name="id"/>
...
<many-to-one name="address"
column="addressId"
not-null="true"
insert="false"
update="false"/>
</class>
<class name="Address">
<id name="id"/>
...
<list name="people">
<key column="addressId" not-null="true"/>
<list-index column="peopleIdx"/>
<one-to-many class="Person"/>
</list>
</class>
如果外键字段是NOT NULL,将位于集合映射中的<key>元素设定为not-null=”true”很重要。不要只在一个可能嵌套的<column>元素上定义not-null=”true”,在<key>元素上也要这样做。
.4.2 一对一
基于外键的双向的一对一关联很常见。
<class name="Person">
<id name="id" column="personId">
<generator class="native"/>
</id>
<many-to-one name="address"
column="addressId"
unique="true"
not-null="true"/>
</class>
<class name="Address">
<id name="id" column="addressId">
<generator class="native"/>
</id>
<one-to-one name="person"
property-ref="address"/>
</class>
create table Person ( personId bigint not null primary key, addressId bigint not null unique )
create table Address ( addressId bigint not null primary key )
注解.4.2: 注意两表中的“address”,此标识可任意取,作用为将两表通过此相同的标识符联接起来。这样,Address知道是person表与其联接。
基于主键的双向的一对一关联使用特殊的id生成器。
<class name="Person">
<id name="id" column="personId">
<generator class="native"/>
</id>
<one-to-one name="address"/>
</class>
<class name="Address">
<id name="id" column="personId">
<generator class="foreign">
<param name="property">person</param>
</generator>
</id>
<one-to-one name="person"
constrained="true"/>
</class>
create table Person ( personId bigint not null primary key )
create table Address ( personId bigint not null primary key )
.5 与连接表的双向关联
.5.1 一对多 / 多对一
基于连接表的一对多关联。注意,inverse=”true”既可放在集合的一端,也可放在连接表的一端。
<class name="Person">
<id name="id" column="personId">
<generator class="native"/>
</id>
<set name="addresses"
table="PersonAddress">
<key column="personId"/>
<many-to-many column="addressId"
unique="true"
class="Address"/>
</set>
</class>
<class name="Address">
<id name="id" column="addressId">
<generator class="native"/>
</id>
<join table="PersonAddress"
inverse="true"
optional="true">
<key column="addressId"/>
<many-to-one name="person"
column="personId"
not-null="true"/>
</join>
</class>
create table Person ( personId bigint not null primary key )
create table PersonAddress ( personId bigint not null, addressId bigint not null primary key )
create table Address ( addressId bigint not null primary key )
.5.2 一对一
基于连接表的双向的一对一关联极端少见,但也可以实现。
<class name="Person">
<id name="id" column="personId">
<generator class="native"/>
</id>
<join table="PersonAddress"
optional="true">
<key column="personId"
unique="true"/>
<many-to-one name="address"
column="addressId"
not-null="true"
unique="true"/>
</join>
</class>
<class name="Address">
<id name="id" column="addressId">
<generator class="native"/>
</id>
<join table="PersonAddress"
optional="true"
inverse="true">
<key column="addressId"
unique="true"/>
<many-to-one name="person"
column="personId"
not-null="true"
unique="true"/>
</join>
</class>
create table Person ( personId bigint not null primary key )
create table PersonAddress ( personId bigint not null primary key, addressId bigint not null unique )
create table Address ( addressId bigint not null primary key )
.5.3 多对多
最后,是双向的多对多关联。
<class name="Person">
<id name="id" column="personId">
<generator class="native"/>
</id>
<set name="addresses" table="PersonAddress">
<key column="personId"/>
<many-to-many column="addressId"
class="Address"/>
</set>
</class>
<class name="Address">
<id name="id" column="addressId">
<generator class="native"/>
</id>
<set name="people" inverse="true" table="PersonAddress">
<key column="addressId"/>
<many-to-many column="personId"
class="Person"/>
</set>
</class>
create table Person ( personId bigint not null primary key )
create table PersonAddress ( personId bigint not null, addressId bigint not null, primary key (personId, addressId) )
create table Address ( addressId bigint not null primary key )
.6 更复杂的关联映射
更复杂的关联极端稀有。Hibernate可以通过在内嵌于映射文件中的SQL片断来处理这种情况。例如,如果一个有历史帐户信息数据的表定义了accountNumber, effectiveEndDate及effectiveStartDate字段,如下:
<properties name="currentAccountKey">
<property name="accountNumber" type="string" not-null="true"/>
<property name="currentAccount" type="boolean">
<formula>case when effectiveEndDate is null then 1 else 0 end</formula>
</property>
</properties>
<property name="effectiveEndDate" type="date"/>
<property name="effectiveStateDate" type="date" not-null="true"/>
这样我们可以通过下面来将一个关联映射到当前实例(即有null effectiveEndDate的):
<many-to-one name="currentAccountInfo"
property-ref="currentAccountKey"
class="AccountInfo">
<column name="accountNumber"/>
<formula>'1'</formula>
</many-to-one>
在一个更复杂的例子中,假如Employee与Organization之间的关联由充斥着历史雇用数据的Employment表来维护。此时,对employee最新的employer的关联(即有最近的startDate者)可以这样映射:
<join>
<key column="employeeId"/>
<subselect>
select employeeId, orgId
from Employments
group by orgId
having startDate = max(startDate)
</subselect>
<many-to-one name="mostRecentEmployer"
class="Organization"
column="orgId"/>
</join>