Chapter 5. O/R Mapping基础

5.1. 映射声明(Mapping declaration)

对象和关系数据库之间的映射是用一个XML文档(XML document)来定义的。这个映射文档被设计为易读的,并且可以手工修改。映射语言是以Java为中心的,意味着映射是按照持久化类的定义来创建的,而非表的定义。

请注意,虽然很多Hibernate用户选择手工定义XML映射文档,也有一些工具来生成映射文档,包括XDoclet,Middlegen和AndroMDA.

让我们从一个映射的例子开始:

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
        "-//Hibernate/Hibernate Mapping DTD 2.0//EN"
        "http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd">

<hibernate-mapping package="eg">

        <class name="Cat" table="CATS" discriminator-value="C">
                <id name="id" column="uid" type="long">
                        <generator class="hilo"/>
                </id>
                <discriminator column="subclass" type="character"/>
                <property name="birthdate" type="date"/>
                <property name="color" not-null="true"/>
                <property name="sex" not-null="true" update="false"/>
                <property name="weight"/>
                <many-to-one name="mate" column="mate_id"/>
                <set name="kittens">
                        <key column="mother_id"/>
                        <one-to-many class="Cat"/>
                </set>
                <subclass name="DomesticCat" discriminator-value="D">
                        <property name="name" type="string"/>
                </subclass>
        </class>

        <class name="Dog">
                <!-- mapping for Dog could go here -->
        </class>

</hibernate-mapping>

我们现在开始讨论映射文档的内容。我们只描述Hibernate在运行时用到的文档元素和属性。映射文档还包括一些额外的可选属性和元素,它们在使用schema导出工具的时候会影响导出的数据库schema结果。(比如, not-null 属性。)

5.1.1. Doctype

所有的XML映射都需要定义如上所示的doctype。DTD可以从上述URL中获取,或者在hibernate-x.x.x/src/net/sf/hibernate目录中,或hibernate.jar文件中找到。Hibernate总是会在它的classptah中首先搜索DTD文件。

5.1.2. hibernate-mapping

这个元素包括三个可选的属性。schema属性,指明了这个映射所引用的表所在的schema名称。假若指定了这个属性,表名会加上所指定的schema的名字扩展为全限定名。假若没有指定,表名就不会使用全限定名。default-cascade指定了未明确注明cascade属性的Java属性和集合类Java会采取什么样的默认级联风格。auto-import属性默认让我们在查询语言中可以使用非全限定名的类名。

<hibernate-mapping
         schema="schemaName"                          (1)
         default-cascade="none|save-update"           (2)
         auto-import="true|false"                     (3)
         package="package.name"                       (4)
 />
1

schema (可选): 数据库schema名称。

2

default-cascade (可选 - 默认为 none): 默认的级联风格。

3

auto-import (可选 - 默认为 true): 指定是否我们可以在查询语言中使用非全限定的类名(仅限于本映射文件中的类)。

4

package (可选): 指定一个包前缀,如果在映射文档中没有指定全限定名,就使用这个包名。

假若你有两个持久化类,它们的非全限定名是一样的(就是在不同的包里面--译者注),你应该设置auto-import="false"。假若说你把一个“import过”的名字同时对应两个类, Hibernate会抛出一个异常。

5.1.3. class

你可以使用class元素来定义一个持久化类:

<class
        name="ClassName"                              (1)
        table="tableName"                             (2)
        discriminator-value="discriminator_value"     (3)
        mutable="true|false"                          (4)
        schema="owner"                                (5)
        proxy="ProxyInterface"                        (6)
        dynamic-update="true|false"                   (7)
        dynamic-insert="true|false"                   (8)
        select-before-update="true|false"             (9)
        polymorphism="implicit|explicit"              (10)
        where="arbitrary sql where condition"         (11)
        persister="PersisterClass"                    (12)
        batch-size="N"                                (13)
        optimistic-lock="none|version|dirty|all"      (14)
        lazy="true|false"                             (15)
/>
1

name: 持久化类(或者接口)的Java全限定名。

2

table: 对应的数据库表名。

3

discriminator-value(辨别值) (可选 - 默认和类名一样):一个用于区分不同的子类的值,在多态行为时使用。

4

mutable(可变) (可选, 默认值为 true): 表明该类的实例可变(不可变)。

5

schema (可选): 覆盖在根<hibernate-mapping>元素中指定的schema名字。

6

proxy (可选): 指定一个接口,在延迟装载时作为代理使用。你可以在这里使用该类自己的名字。

7

dynamic-update(动态更新) (可选,默认为false): 指定用于UPDATE 的SQL将会在运行时动态生成,并且只更新那些改变过的字段。

8

dynamic-insert(动态插入) (可选, 默认为false): 指定用于INSERT的 SQL 将会在运行时动态生成,并且只包含那些非空值字段。

9

select-before-update (可选,默认值为false): 指定Hibernate除非确定对象的确被修改了,不会执行SQL UPDATE操作。在特定场合(实际上,只会发生在一个临时对象关联到一个新的session中去,执行update()的时候),这说明Hibernate会在UPDATE之前执行一次额外的SQL SELECT操作,来决定是否应该进行UPDATE

10

polymorphism(多形,多态) (可选, 默认值为 implicit (隐式)): 界定是隐式还是显式的使用查询多态。

(11)

where (可选) 指定一个附加的SQLWHERE 条件,在抓取这个类的对象时会一直增加这个条件。

(12)

persister (可选): 指定一个定制的ClassPersister

(13)

batch-size (可选,默认是1) 指定一个用于根据标识符抓取实例时使用的"batch size"(批次抓取数量)。

(14)

optimistic-lock(乐观锁定) (可选,默认是version): 决定乐观锁定的策略。

(15)

lazy(延迟) (可选): 假若设置 lazy="true",就是设置这个类自己的名字作为proxy接口的一种等价快捷形式。

若指明的持久化类实际上是一个接口,也可以被完美地接受。其后你可以用<subclass>来指定该接口的实际实现类名。你可以持久化任何static(静态的)内部类。记得应该使用标准的类名格式,就是说比如:Foo$Bar

不可变类,mutable="false"不可以被应用程序更新或者删除。这可以让Hibernate做一些小小的性能优化。

可选的proxy属性可以允许延迟加载类的持久化实例。Hibernate开始会返回实现了这个命名接口的CGLIB代理。当代理的某个方法被实际调用的时候,真实的持久化对象才会被装载。参见下面的“用于延迟装载的代理”。

Implicit (隐式)的多态是指,如果查询中给出的是任何超类、该类实现的接口或者该类的名字,都会返回这个类的实例;如果查询中给出的是子类的名字,则会返回子类的实例。 Explicit (显式)的多态是指,只有在查询中给出的明确是该类的名字时才会返回这个类的实例;同时只有当在这个<class>的定义中作为<subclass>或者<joined-subclass>出现的子类,才会可能返回。 大多数情况下,默认的polymorphism="implicit"都是合适的。 显式的多态在有两个不同的类映射到同一个表的时候很有用。(允许一个“轻型”的类,只包含部分表字段)。

persister属性可以让你定制这个类使用的持久化策略。你可以指定你自己实现的net.sf.hibernate.persister.EntityPersister的子类,你甚至可以完全从头开始编写一个net.sf.hibernate.persister.ClassPersister接口的实现,可能是用储存过程调用、序列化到文件或者LDAP数据库来实现的。参阅net.sf.hibernate.test.CustomPersister,这是一个简单的例子(“持久化”到一个Hashtable)。

请注意dynamic-updatedynamic-insert的设置并不会继承到子类,所以在<subclass>或者<joined-subclass>元素中可能需要再次设置。这些设置是否能够提高效率要视情形而定。请用你的智慧决定是否使用。

使用select-before-update通常会降低性能.当是在防止数据库不必要的触发update触发器,这就很有用了。

如果你打开了dynamic-update,你可以选择几种乐观锁定的策略:

  • version(版本检查) 检查version/timestamp字段

  • all(全部) 检查全部字段

  • dirty(脏检查)只检察修改过的字段

  • none(不检查)不使用乐观锁定

We very strongly recommend that you use version/timestamp columns for optimistic locking with Hibernate. This is the optimal strategy with respect to performance and is the only strategy that correctly handles modifications made outside of the session (ie. when Session.update() is used). 我们非常强烈建议你在Hibernate中使用version/timestamp字段来进行乐观锁定。对性能来说,这是最好的选择,并且这也是唯一能够处理在session外进行操作的策略(就是说,当使用Session.update()的时候)。

5.1.4. id

被映射的类必须声明对应数据库表主键字段。大多数类有一个JavaBeans风格的属性,为每一个实例包含唯一的标识。<id> 元素定义了该属性到数据库表主键字段的映射。

<id
        name="propertyName"                      (1)
        type="typename"                          (2)
        column="column_name"                     (3)
        unsaved-value="any|none|null|id_value"   (4)
        access="field|property|ClassName">       (5)

        <generator class="generatorClass"/>
</id>
1

name (可选): 标识属性的名字。

2

type (可选): 标识Hibernate类型的名字。

3

column (可选 - 默认为属性名): 主键字段的名字。

4

unsaved-value (可选 - 默认为null): 一个特定的标识属性值,用来标志该实例是刚刚创建的,尚未保存。这可以把这种实例和从以前的session中装载过(可能又做过修改--译者注)但未再次持久化的实例区分开来。

5

access (可选 - 默认为property): Hibernate用来访问属性值的策略。

如果 name属性不存在,会认为这个类没有标识属性。

unsaved-value 属性很重要!如果你的类的标识属性不是默认为null的,你应该指定正确的默认值。

还有一个另外的<composite-id>声明可以访问旧式的多主键数据。我们强烈不鼓励使用这种方式。

5.1.4.1. generator

必须声明的<generator>子元素是一个Java类的名字,用来为该持久化类的实例生成唯一的标识。如果这个生成器实例需要某些配置值或者初始化参数,用<param>元素来传递。

<id name="id" type="long" column="uid" unsaved-value="0">
        <generator class="net.sf.hibernate.id.TableHiLoGenerator">
                <param name="table">uid_table</param>
                <param name="column">next_hi_value_column</param>
        </generator>
</id>

所有的生成器都实现net.sf.hibernate.id.IdentifierGenerator接口。这是一个非常简单的接口;某些应用程序可以选择提供他们自己特定的实现。当然,Hibernate提供了很多内置的实现。下面是一些内置生成器的快捷名字:

increment(递增)

用于为long, short或者int类型生成唯一标识。只有在没有其他进程往同一张表中插入数据时才能使用。 在集群下不要使用。

identity

对DB2,MySQL, MS SQL Server, Sybase和HypersonicSQL的内置标识字段提供支持。返回的标识符是long, short 或者int类型的。

sequence (序列)

在DB2,PostgreSQL, Oracle, SAP DB, McKoi中使用序列(sequence),而在Interbase中使用生成器(generator)。返回的标识符是long, short或者 int类型的。

hilo (高低位)

使用一个高/低位算法来高效的生成long, short或者 int类型的标识符。给定一个表和字段(默认分别是是hibernate_unique_keynext)作为高位值得来源。高/低位算法生成的标识符只在一个特定的数据库中是唯一的。在使用JTA获得的连接或者用户自行提供的连接中,不要使用这种生成器。

seqhilo(使用序列的高低位)

使用一个高/低位算法来高效的生成long, short或者 int类型的标识符,给定一个数据库序列(sequence)的名字。

uuid.hex

用一个128-bit的UUID算法生成字符串类型的标识符。在一个网络中唯一(使用了IP地址)。UUID被编码为一个32位16进制数字的字符串。

uuid.string

使用同样的UUID算法。UUID被编码为一个16个字符长的任意ASCII字符组成的字符串。不能使用在PostgreSQL数据库中

native(本地)

根据底层数据库的能力选择identity, sequence 或者hilo中的一个。

assigned(程序设置)

让应用程序在save()之前为对象分配一个标示符。

foreign(外部引用)

使用另外一个相关联的对象的标识符。和<one-to-one>联合一起使用。

5.1.4.2. 高/低位算法(Hi/Lo Algorithm)

hiloseqhilo生成器给出了两种hi/lo算法的实现,这是一种很令人满意的标识符生成算法。第一种实现需要一个“特殊”的数据库表来保存下一个可用的“hi”值。第二种实现使用一个Oracle风格的序列(在被支持的情况下)。

<id name="id" type="long" column="cat_id">
        <generator class="hilo">
                <param name="table">hi_value</param>
                <param name="column">next_value</param>
                <param name="max_lo">100</param>
        </generator>
</id>
<id name="id" type="long" column="cat_id">
        <generator class="seqhilo">
                <param name="sequence">hi_value</param>
                <param name="max_lo">100</param>
        </generator>
</id>

很不幸,你在为Hibernate自行提供Connection,或者Hibernate使用JTA获取应用服务器的数据源连接的时候无法使用hilo 。Hibernate必须能够在一个新的事务中得到一个"hi"值。在EJB环境中实现hi/lo算法的标准方法是使用一个无状态的session bean。

5.1.4.3. UUID算法(UUID Algorithm )

UUID饱含:IP地址,JVM的启动时间(精确到1/4秒),系统时间和一个计数器值(在JVM中唯一)。在Java代码中不可能获得MAC地址或者内存地址,所以这已经是我们在不使用JNI的前提下的能做的最好实现了。

不要试图在PostgreSQL中使用uuid.string

5.1.4.4. 标识字段和序列(Identity Columns and Sequences)

对于内部支持标识字段的数据库(DB2,MySQL,Sybase,MS SQL),你可以使用identity关键字生成。对于内部支持序列的数据库(DB2,Oracle, PostgreSQL, Interbase, McKoi,SAP DB),你可以使用sequence风格的关键字生成。这两种方式对于插入一个新的对象都需要两次SQL查询。

<id name="id" type="long" column="uid">
        <generator class="sequence">
                <param name="sequence">uid_sequence</param>
        </generator>
</id>
<id name="id" type="long" column="uid" unsaved-value="0">
        <generator class="identity"/>
</id>

对于跨平台开发,native策略会从identity, sequencehilo中进行选择,取决于底层数据库的支持能力。

5.1.4.5. 程序分配的标识符(Assigned Identifiers)

如果你需要应用程序分配一个标示符(而非Hibernate来生成它们),你可以使用assigned生成器。这种特殊的生成器会使用已经分配给对象的标识符属性的标识符值。用这种特性来分配商业行为的关键字要特别小心(基本上总是一种可怕的设计决定)。

5.1.5. composite-id 联合ID

<composite-id
        name="propertyName"
        class="ClassName"
        unsaved-value="any|none">

        <key-property name="propertyName" type="typename" column="column_name"/>
        <key-many-to-one name="propertyName" class="ClassName" column="column_name"/>
        ......
</composite-id>

如果表使用联合主键,你可以把类的多个属性组合成为标识符属性。<composite-id>元素接受<key-property>属性映射和<key-many-to-one>属性映射作为子元素。

<composite-id>
        <key-property name="medicareNumber"/>
        <key-property name="dependent"/>
</composite-id>

你的持久化类必须重载equals()hashCode()方法,来实现组合的标识符判断等价.也必须实现Serializable接口。

不幸的是,这种组合关键字的方法意味着一个持久化类是它自己的标识。除了对象自己之外,没有什么方便的“把手”可用。你必须自己初始化持久化类的实例,在使用组合关键字load()持久化状态之前,必须填充他的联合属性。我们会在Section 7.4, “作为联合标识符(As Composite Identifiers)”章中说明一种更加方便的方法,把联合标识实现为一个独立的类,下面描述的属性只对这种备用方法有效:

  • name (可选):一个组件类型,持有联合标识(参见下一节)。

  • class (可选 - 默认为通过反射(reflection)得到的属性类型) : 作为联合标识的组件类名(参见下一节)。

  • unsaved-value (可选 - 默认为 none): 假如被设置为非none的值,就表示新创建,尚未被持久化的实例将持有的值。

5.1.6. 识别器(discriminator)

在"一棵对象继承树对应一个表"的策略中,<discriminator>元素是必需的,它声明了表的识别器字段。识别器字段包含标志值,用于告知持久化层应该为某个特定的行创建哪一个子类的实例。只能使用如下受到限制的一些类型: string, character, integer, byte, short, boolean, yes_no, true_false.

<discriminator
        column="discriminator_column"  (1)
        type="discriminator_type"      (2)
        force="true|false"             (3)
/>
1

column (可选 - 默认为 class) 识别器字段的名字

2

type (可选 - 默认为 string) 一个Hibernate字段类型的名字

3

force(强制) (可选 - 默认为 false) "强制"Hibernate指定允许的识别器值,就算取得的所有实例都是根类的。

标识器字段的实际值是根据<class><subclass>元素的discriminator-value得来的.

force属性仅仅是在表包含一些未指定应该映射到哪个持久化类的时候才是有用的。这种情况不是经常会遇到。

5.1.7. 版本(version)(可选)

<version>元素是可选的,表明表中包含附带版本信息的数据。这在你准备使用 长事务(long transactions)的时候特别有用。(见后)

<version
        column="version_column"   (1)
        name="propertyName"       (2)
        type="typename"           (3)
        access="field|property|ClassName"                  (4)
        unsaved-value="null|negative|undefined"            (5)
/>
1

column (可选 - 默认为属性名): 指定持有版本号的字段名。

2

name: 持久化类的属性名。

3

type (可选 - 默认是 integer): 版本号的类型。

4

access (可选 - 默认是 property): Hibernate用于访问属性值的策略。

5

unsaved-value (可选 - 默认是undefined): 用于标明某个实例时刚刚被实例化的(尚未保存)版本属性值,依靠这个值就可以把这种情况和已经在先前的session中保存或装载的实例区分开来。(undefined指明使用标识属性值进行这种判断。)

版本号必须是以下类型:long, integer, short, timestamp或者calendar

5.1.8. 时间戳(timestamp )(可选)

可选的<timestamp>元素指明了表中包含时间戳数据。这用来作为版本的替代。时间戳本质上是一种对乐观锁定的一种不是特别安全的实现。当然,有时候应用程序可能在其他方面使用时间戳。

<timestamp
        column="timestamp_column"      (1)
        name="propertyName"            (2)
        access="field|property|ClassName"   (3)
        unsaved-value="null|undefined"      (4)
/>
1

column (可选 - 默认为属性名): 持有时间戳的字段名。

2

name: 在持久化类中的JavaBeans风格的属性名,其Java类型是 Date 或者 Timestamp的。

3

access (可选 - 默认是 property): Hibernate用于访问属性值的策略。

4

unsaved-value (可选 - 默认是null): 用于标明某个实例时刚刚被实例化的(尚未保存)版本属性值,依靠这个值就可以把这种情况和已经在先前的session中保存或装载的实例区分开来。(undefined指明使用标识属性值进行这种判断。)

注意,<timestamp><version type="timestamp">是等价的。

5.1.9. property

<property>元素为类声明了一个持久化的,JavaBean风格的属性。

<property
        name="propertyName"                 (1)
        column="column_name"                (2)
        type="typename"                     (3)
        update="true|false"                 (4)
        insert="true|false"                 (4)
        formula="arbitrary SQL expression"  (5)
        access="field|property|ClassName"   (6)
/>
1

name: 属性的名字,以小写字母开头。

2

column (可选 - 默认为属性名字): 对应的数据库字段名。

3

type (可选): 一个Hibernate类型的名字。

4

update, insert (可选 - 默认为 true) :表明在用于UPDATE 和/或 INSERT的SQL语句中是否包含这个字段。这二者如果都设置为false则表明这是一个“外源性(derived)”的属性,它的值来源于映射到同一个(或多个)字段的某些其他属性,或者通过一个trigger(触发器),或者其他程序。

5

formula (可选): 一个SQL表达式,定义了这个计算(computed) 属性的值。计算属性没有和它对应的数据库字段。

6

access (可选 - 默认值为 property): Hibernate用来访问属性值的策略。

typename可以是如下几种:

  1. Hibernate基础类型之一(比如:integer, string, character,date, timestamp, float, binary, serializable, object, blob)。

  2. 一个Java类的名字,这个类属于一种默认基础类型 (比如: int, float,char, java.lang.String, java.util.Date, java.lang.Integer, java.sql.Clob)。

  3. 一个PersistentEnum的子类的名字。(比如:. eg.Color)。

  4. 一个可以序列化的Java类的名字。

  5. 一个自定义类型的类的名字。(比如: com.illflow.type.MyCustomType)。

如果你没有指定类型,Hibernarte会使用反射来得到这个名字的属性,以此来猜测正确的Hibernate类型。Hibernate会对属性读取器(getter方法)的返回类进行解释,按照规则2,3,4的顺序。然而,这并不足够。 在某些情况下你仍然需要type属性。(比如,为了区别Hibernate.DATEHibernate.TIMESTAMP,或者为了指定一个自定义类型。)

access属性用来让你控制Hibernate如何在运行时访问属性。在默认情况下,Hibernate会使用属性的get/set方法对。如果你指明access="field",Hibernate会忽略get/set方法对,直接使用反射来访问成员变量。你也可以指定你自己的策略,这就需要你自己实现net.sf.hibernate.property.PropertyAccessor接口,再在access中设置你自定义策略类的名字。

5.1.10. 多对一(many-to-one)

通过many-to-one元素,可以定义一种常见的与另一个持久化类的关联。这种关系模型是多对一关联。(实际上是一个对象引用。)

<many-to-one
        name="propertyName"                      (1)
        column="column_name"                     (2)
        class="ClassName"                        (3)
        cascade="all|none|save-update|delete"    (4)
        outer-join="true|false|auto"             (5)
        update="true|false"                      (6)
        insert="true|false"                      (6)
        property-ref="propertyNameFromAssociatedClass"     (7)
        access="field|property|ClassName"                  (8)
/>
1

name: 属性名。

2

column (可选): 字段名。

3

class (可选 - 默认是通过反射得到属性类型): 关联的类的名字。

4

cascade(级联) (可选): 指明哪些操作会从父对象级联到关联的对象。

5

outer-join(外连接) (可选 - 默认为 自动): 当设置hibernate.use_outer_join的时候,对这个关联允许外连接抓取。

6

update, insert (可选 - defaults to true) 指定对应的字段是否在用于UPDATE 和/或 INSERT的SQL语句中包含。如果二者都是false,则这是一个纯粹的“外源性(derived)”关联,它的值是通过映射到同一个(或多个)字段的某些其他属性得到的,或者通过trigger(除法器),或者是其他程序。

7

property-ref: (可选) 指定关联类的一个属性,这个属性将会和本外键相对应。如果没有指定,会使用对方关联类的主键。

8

access (可选 - 默认是 property): Hibernate用来访问属性的策略。

cascade 属性允许下列值: all, save-update, delete, none。设置除了none以外的其它值会传播特定的操作到关联的(子)对象中。参见后面的“Lifecycle Objects(自动管理生命周期的对象)”。

outer-join参数允许下列三个不同值:

  • auto (默认) 使用外连接抓取关联(对象),如果被关联的对象没有代理(proxy)

  • true 一直使用外连接来抓取关联

  • false 永远不使用外连接来抓取关联

一个典型的简单many-to-one声明例子:

<many-to-one name="product" class="Product" column="PRODUCT_ID"/>

property-ref属性只应该用来对付老旧的数据库系统,可能出现外键指向的对方关联表的某个非主键字段(但是应该是一个惟一关键字)的情况。这是一种十分丑陋的关系模型。比如说,假设Product类有一个惟一的序列号,它并不是主键。

<property name="serialNumber" unique="true" type="string" column="SERIAL_NUMBER"/>

那么关于OrderItem 的映射可能是:

<many-to-one name="product" property-ref="serialNumber" column="PRODUCT_SERIAL_NUMBER"/>

当然,我们决不鼓励这种用法。

5.1.11. one-to-one

5.1.11. one-to-one

持久化对象之间一对一的关联关系是通过one-to-one元素定义的。

<one-to-one
        name="propertyName"                      (1)
        class="ClassName"                        (2)
        cascade="all|none|save-update|delete"    (3)
        constrained="true|false"                 (4)
        outer-join="true|false|auto"             (5)
        property-ref="propertyNameFromAssociatedClass"     (6)
        access="field|property|ClassName"                  (7)
        
/>
1

name: 属性的名字。

2

class (可选 - 默认是通过反射得到的属性类型):被关联的类的名字。

3

cascade(级联) (可选) 表明操作是否从父对象级联到被关联的对象。

4

constrained(约束) (可选) 表明该类对应的表对应的数据库表,和被关联的对象所对应的数据库表之间,通过一个外键引用对主键进行约束。这个选项影响save()delete()在级联执行时的先后顺序(也在schema export tool中被使用)。

5

outer-join(外连接) (可选 - 默认为 自动): 当设置hibernate.use_outer_join的时候,对这个关联允许外连接抓取。

6

property-ref: (可选) 指定关联类的一个属性,这个属性将会和本外键相对应。如果没有指定,会使用对方关联类的主键。

6

access (可选 - 默认是 property): Hibernate用来访问属性的策略。

There are two varieties of one-to-one association: 有两种不同的一对一关联:

  • 主键关联

  • 惟一外键关联

主键关联不需要额外的表字段;两行是通过这种一对一关系相关联的,那么这两行就共享同样的主关键字值。所以如果你希望两个对象通过主键一对一关联,你必须确认它们被赋予同样的标识值!

比如说,对下面的EmployeePerson进行主键一对一关联:

<one-to-one name="person" class="Person" constrained="true"/>
<one-to-one name="employee" class="Employee"/>

另一种方式是一个外键和一个惟一关键字对应,上面的EmployeePerson的例子,如果使这种关联方式,应该表达成:

<many-to-one name="person" class="Person" column="PERSON_ID" unique="true"/>

如果在Person的映射加入下面几句,这种关联就是双向的:

<one-to-one name"employee" class="Employee" property-ref="person"/>

5.1.12. 组件(component), 动态组件(dynamic-component)

<component>元素把子对象的一些元素与父类对应的表的一些字段映射起来。 然后组件可以声明它们自己的属性、组件或者集合。参见后面的“Components”一章。

<component 
        name="propertyName"                 (1)
        class="className"                   (2)
        insert="true|false"                 (3)
        upate="true|false"                  (4)
        access="field|property|ClassName">  (5)
        
        <property ...../>
        <many-to-one .... />
        ........
</component>
1

name: 属性名

2

class (可选 - 默认为通过反射得到的属性类型):组件(子)类的名字。

3

insert: 被映射的字段是否出现在SQL的INSERT语句中?

4

update: 被映射的字段是否出现在SQL的UPDATE语句中?

5

access (可选 - 默认是 property): Hibernate用来访问属性的策略。

<property>子标签为子类的一些属性和表字段建立映射。

<component>元素允许加入一个<parent>子元素,在组件类内部就可以有一个指向其容器的实体的反向引用。

<dynamic-component>元素允许把一个Map映射为组件,其属性名对应map的键值。

5.1.13. 子类(subclass)

最后,多态持久化需要为父类的每个子类都进行声明。对于我们建议的“每一棵类继承树对应一个表”的策略来说,就需要使用<subclass>声明。

<subclass
        name="ClassName"                              (1)
        discriminator-value="discriminator_value"     (2)
        proxy="ProxyInterface"                        (3)
        lazy="true|false"                             (4)
        dynamic-update="true|false"
        dynamic-insert="true|false">

        <property .... />
        .....
</subclass>
1

name: 子类的全限定名。

2

discriminator-value(辨别标志) (可选 - 默认为类名):一个用于区分每个独立的子类的值。

3

proxy(代理) (可选): 指定一个类或者接口,在延迟装载时作为代理使用。

4

lazy(延迟装载) (可选): 设置lazy="true"是把自己的名字作为proxy接口的一种等价快捷方式。

每个子类都应该声明它自己的持久化属性和子类。 <version><id> 属性可以从根父类继承下来。在一棵继承树上的每个子类都必须声明一个唯一的discriminator-value。如果没有指定,就会使用Java类的全限定名。

5.1.14. 连接的子类(joined-subclass)

另外一种情况,如果子类是持久化到一个属于它自己的表(每一个子类对应一个表的映射策略),那么就需要使用<joined-subclass>元素。

<joined-subclass
        name="ClassName"          (1)
        proxy="ProxyInterface"    (2)
        lazy="true|false"                   (3)
        dynamic-update="true|false"
        dynamic-insert="true|false">

        <key .... >

        <property .... />
        .....
</subclass>
1

name: 子类的全限定名。

2

proxy (可选): 指定一个类或者接口,在延迟装载时作为代理使用。

3

lazy(延迟装载) (可选): 设置lazy="true"是把自己的名字作为proxy接口的一种等价快捷方式。

这种映射策略不需要指定辨别标志(discriminator)字段。但是,每一个都必须使用<key>元素指定一个表字段包含对象的标识符。本章开始的映射可以被用如下方式重写:

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
        "-//Hibernate/Hibernate Mapping DTD//EN"
        "http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd">

<hibernate-mapping  package="eg">

        <class name="Cat" table="CATS">
                <id name="id" column="uid" type="long">
                        <generator class="hilo"/>
                </id>
                <property name="birthdate" type="date"/>
                <property name="color" not-null="true"/>
                <property name="sex" not-null="true"/>
                <property name="weight"/>
                <many-to-one name="mate"/>
                <set name="kittens">
                        <key column="MOTHER"/>
                        <one-to-many class="Cat"/>
                </set>
                <joined-subclass name="DomesticCat" table="DOMESTIC_CATS">
                	<key column="CAT"/>
                        <property name="name" type="string"/>
                </joined-subclass>
        </class>

        <class name="eg.Dog">
                <!-- mapping for Dog could go here -->
        </class>

</hibernate-mapping>

5.1.15. map, set, list, bag

集合类在后面讨论。

5.1.16. 引用(import)

假设你的应用程序有两个同样名字的持久化类,但是你不想在Hibernate查询中使用他们的全限定名。除了依赖auto-import="true"以外,类也可以被显式地“import(引用)”。你甚至可以引用没有明确被映射的类和接口。

<import class="java.lang.Object" rename="Universe"/>
<import
        class="ClassName"                             (1)
        rename="ShortName"                            (2)
/>
1

class: 任何Java类的全限定名。

2

rename (可选 - 默认为类的全限定名): 在查询语句中可以使用的名字。

5.2. Hibernate 的类型

5.2.1. 实体(Entities)和值(values)

为了理解很多与持久化服务相关的Java语言级对象的行为,我们需要把它们分为两类:

实体entity 独立于任何持有实体引用的对象。与通常的Java模型相比,不再被引用的对象会被当作垃圾收集掉。实体必须被显式的保存和删除(除非保存和删除是从父实体向子实体引发的级联)。这和ODMG模型中关于对象通过可触及保持持久性有一些不同——比较起来更加接近应用程序对象通常在一个大系统中的使用方法。实体支持循环引用和交叉引用,它们也可以加上版本信息。

实体的持久化状态包含有指向其他实体的连接和一些 类型的实例。值是原始类型、集合、组件或者特定的不可变对象。与实体不同,值(特别是集合和组件)通过可触及性来进行持久化和删除的。因为值对象(和原始类型数据)是随着包含它们的实体而被持久化和删除的,它们不能够被独立的加上版本信息。值没有独立的标识,所以它们不能被两个实体或者集合共享。

所有的Hibernate对象,除了集合,都支持null语义。

直到现在,我们都一直使用"持久化对象"来代表实体。我们仍然会这么做。然而严格的来说,并不是所有用户定义的,带有持久化状态的类都是实体。组件(component)就是一个用户定义的类,仅仅由值语义构成。

5.2.2. 基本值类型

基本类型可以大致的分为:

integer, long, short, float, double, character, byte, boolean, yes_no, true_false

这些类型都对应Java的原始类型或者其包装类,来适合(特定厂商的)SQL 字段类型。boolean, yes_notrue_false都是Java 中boolean 或者java.lang.Boolean的另外说法。

string

java.lang.StringVARCHAR (或者 Oracle的 VARCHAR2)的映射。

date, time, timestamp

java.util.Date和其子类到SQL类型DATE, TIMETIMESTAMP (或等价类型)的映射。

calendar, calendar_date

java.util.Calendar 到SQL 类型TIMESTAMPDATE(或等价类型)的映射。

big_decimal

java.math.BigDecimalNUMERIC (或者 Oracle 的NUMBER类型)的映射。

locale, timezone, currency

java.util.Locale, java.util.TimeZonejava.util.CurrencyVARCHAR (或者 Oracle 的VARCHAR2类型)的映射. LocaleCurrency 的实例被映射为它们的ISO代码。TimeZone的实例被影射为它的ID

class

java.lang.ClassVARCHAR (或者 Oracle 的VARCHAR2类型)的映射。Class被映射为它的全限定名。

binary

把字节数组(byte arrays)映射为对应的 SQL二进制类型。

text

把长Java字符串映射为SQL的CLOB或者TEXT类型。

serializable

把可序列化的Java类型映射到对应的SQL二进制类型。你也可以为一个并非默认为基本类型或者实现PersistentEnum接口的可序列化Java类或者接口指定Hibernate类型serializable

clob, blob

JDBC 类 java.sql.Clobjava.sql.Blob的映射。某些程序可能不适合使用这个类型,因为blob和clob对象可能在一个事务之外是无法重用的。(而且, 驱动程序对这种类型的支持充满着补丁和前后矛盾。)

实体及其集合的唯一标识可以是任何基础类型,除了binaryblobclob之外。(联合标识也是允许的,后面会说到。)

net.sf.hibernate.Hibernate中,定义了基础类型对应的Type常量。比如,Hibernate.STRING代表string 类型。

5.2.3. 持久化枚举(Persistent enum)类型

枚举(enumerated)类型是一种常见的Java习惯用语,它是一个类,拥有一些(不多)的不可变实例。你可以为枚举类型实现net.sf.hibernate.PersistentEnum接口,定义toInt()fromInt()方法:

package eg;
import net.sf.hibernate.PersistentEnum;

public class Color implements PersistentEnum {
    private final int code;
    private Color(int code) {
        this.code = code;
    }
    public static final Color TABBY = new Color(0);
    public static final Color GINGER = new Color(1);
    public static final Color BLACK = new Color(2);

    public int toInt() { return code; }

    public static Color fromInt(int code) {
        switch (code) {
            case 0: return TABBY;
            case 1: return GINGER;
            case 2: return BLACK;
            default: throw new RuntimeException("Unknown color code");
        }
    }
}

Hibernate可以使用枚举类的名字作为类型名,这个例子中就是eg.Color

5.2.4. 自定义值类型

开发者创建属于他们自己的值类型也是很容易的。比如说,你可能希望持久化java.lang.BigInteger类型的属性,持久化成为VARCHAR字段。Hibernate没有内置这样一种类型。自定义类型能够映射一个属性(或集合元素)到不止一个数据库表字段。比如说,你可能有这样的Java属性:getName()/setName(),这是java.lang.String类型的,对应的持久化到三个字段:FIRST_NAME, INITIAL, SURNAME

要实现一个自定义类型,可以实现net.sf.hibernate.UserTypenet.sf.hibernate.CompositeUserType中的任一个,并且使用类型的Java全限定类名来声明属性。请查看net.sf.hibernate.test.DoubleStringType这个例子,看看它是怎么做的。

<property name="twoStrings" type="net.sf.hibernate.test.DoubleStringType">
    <column name="first_string"/>
    <column name="second_string"/>
</property>

注意使用<column>标签来把一个属性映射到多个字段的做法。

虽然Hibernate内置的丰富类型和对component的支持意味着你可能很少需要使用自定义类型,至少对于你程序中经常出现的自定义类(并非实体)来说,这是一种好方法。比如说,MonetoryAmount(价格总额)对比使用CompositeUserType来说更好,虽然它可以很容易的使用一个component实现。这样做的动机之一是抽象。通过自定义类型,以后假若你改变表示金额值的方法时,你的映射文件不需要更改,这就得到了保护。

5.2.5. 映射到"任意"(any)类型

这是属性映射的又一种类型。<any>映射元素定义了一种从多个表到类的多形联合。这种类型的映射总是需要多于一个字段。第一个字段持有被从属的实体的类型。其他的字段持有标识符。对于这种类型的联合来说,不可能指定一个外键约束,所以当然这不是(多形)联合映射的通常方式。你只应该在非常特殊的情况下使用它(比如,审计log,用户会话数据等等)。

<any name="anyEntity" id-type="long" meta-type="eg.custom.Class2TablenameType">
    <column name="table_name"/>
    <column name="id"/>
</any>

meta-type属性让应用程序指定一个自定义类型,把数据库字段值映射到一个持久化类,该类的标识属性是用id-type定义的。如果meta-type返回java.lang.Class的实例,不需要其他处理。另一方面,如果是类似string或者character这样的基本类型,你必须指定从值到类的映射。

<any name="anyEntity" id-type="long" meta-type="string">
    <meta-value value="TBL_ANIMAL" class="Animal"/>
    <meta-value value="TBL_HUMAN" class="Human"/>
    <meta-value value="TBL_ALIEN" class="Alien"/>
    <column name="table_name"/>
    <column name="id"/>
</any>
<any
        name="propertyName"              (1)
        id-type="idtypename"             (2)
        meta-type="metatypename"         (3)
        cascade="none|all|save-update"   (4)
        access="field|property|ClassName"        (5)
>
        <meta-value ... />
        <meta-value ... />
        .....
        <column .... />
        <column .... />
        .....
</any>
1

name: 属性名。

2

id-type: 标识符类型。

3

meta-type (可选 - 默认为class): 一个用于把java.lang.Class映射到一个数据库字段的类或者允许分辨映射的类型。

4

cascade(级联) (可选- 默认为 none): 级联风格。

5

access (可选 - 默认是 property): Hibernate用来访问属性的策略。

老式的object 类型是用来在Hibernate 1.2中起到类似作用的,他仍然被支持,但是已经基本废弃了。

5.3. SQL中引号包围的标识符

你可强制Hibernate在生成的SQL中把标识符用引号前后包围起来,这需要在映射文档中使用反向引号(`)把表名或者字段名包围(可能比较拗口,请看下面的例子)。Hibernate会使用相应的SQLDialect(方言)来使用正确的引号风格(通常是双引号,但是在SQL Server中是括号,MySQL中是反向引号)。

<class name="LineItem" table="`Line Item`">
    <id name="id" column="`Item Id`"/><generator class="assigned"/></id>
    <property name="itemNumber" column="`Item #`"/>
    ...
</class>

5.4. 自定义DDL

Hibernate映射文档也包含一些只为了SchemaExport命令行工具生成DDL使用的信息。比如,你可以使用<column>元素的sql-type属性覆盖字段类型。

<property 
    name="amount" 
    type="big_decimal">
    <column 
        name="AMOUNT" 
        sql-type="NUMERIC(11, 2)"/>
</property>

或者,你可以指定字段长度和约束。下面是等价的:

<property 
    name="socialSecurityNumber" 
    type="string" 
    length="9" 
    column="SSN"
    not-null="true" 
    unique="true"/>

<property 
    name="socialSecurityNumber" 
    type="string">
    <column 
        name="SSN" 
        length="9" 
        not-null="true" 
        unique="true"/>
</property>

5.5. 映射文件的模块化(Modular mapping files)

It is possible to define subclass and joined-subclass mappings in seperate mapping documents, directly beneath hibernate-mapping. This allows you to extend a class hierachy just by adding a new mapping file. You must specify an extends attribute in the subclass mapping, naming a previously mapped superclass. Use of this feature makes the ordering of the mapping documents important! 允许在独立的映射文档中定义subclassjoined-subclass,直接位于hibernate-mapping下。这就可以让你每次扩展你的类层次的时候,加入新的映射文件就行了。在子类的映射中你必须指定一个extents属性,指明先前已经映射过的超类。使用这个功能的时候,一定要注意映射文件的排序是非常重要的!

<hibernate-mapping>
        <subclass name="eg.subclass.DomesticCat" extends="eg.Cat" discriminator-value="D">
             <property name="name" type="string"/>
        </subclass>
</hibernate-mapping>