目录
对象和关系数据库之间的映射通常是用一个XML文档(XML document)来定义的。这个映射文档被设计为易读的, 并且可以手工修改。映射语言是以Java为中心,这意味着映射文档是按照持久化类的定义来创建的, 而非表的定义。
请注意,虽然很多Hibernate用户选择手写XML映射文档,但也有一些工具可以用来生成映射文档, 包括XDoclet,Middlegen和AndroMDA。
让我们从一个映射的例子开始:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="eg">
<class name="Cat"
table="cats"
discriminator-value="C">
<id name="id">
<generator class="native"/>
</id>
<discriminator column="subclass"
type="character"/>
<property name="weight"/>
<property name="birthdate"
type="date"
not-null="true"
update="false"/>
<property name="color"
type="eg.types.ColorUserType"
not-null="true"
update="false"/>
<property name="sex"
not-null="true"
update="false"/>
<property name="litterId"
column="litterId"
update="false"/>
<many-to-one name="mother"
column="mother_id"
update="false"/>
<set name="kittens"
inverse="true"
order-by="litter_id">
<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 属性。)
所有的XML映射都需要定义如上所示的doctype。DTD可以从上述URL中获取,
也可以从hibernate-x.x.x/src/net/sf/hibernate目录中、
或hibernate.jar文件中找到。Hibernate总是会首先在它的classptah中搜索DTD文件。
如果你发现它是通过连接Internet查找DTD文件,就对照你的classpath目录检查XML文件里的DTD声明。
As mentioned previously, Hibernate will first attempt to resolve DTDs in its classpath. The
manner in which it does this is by registering a custom org.xml.sax.EntityResolver
implementation with the SAXReader it uses to read in the xml files. This custom
EntityResolver recognizes two different systemId namespaces.
如前所述,Hibernate首先在其classpath中查找DTD。其行为是依靠在系统中注册的org.xml.sax.EntityResolver的一个具体实现,SAXReader依靠它来读取xml文件。这一 EntityResolver 实现能辨认两种不同的 systenId命名空间。
若resolver遇到了一个以http://hibernate.sourceforge.net/为开头的systemId,它会辨认出是hibernate namespace,resolver就试图通过加载Hibernate类的classloader来查找这些实体。
若resolver遇到了一个使用classpath://URL协议的systemId,它会辨认出这是user namespace,resolver试图通过(1)当前线程上下文的classloader和(2)加载Hibernate class的classloader来查找这些实体。
使用user namespace(用户命名空间)的例子:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd" [
<!ENTITY types SYSTEM "classpath://your/domain/types.xml">
]>
<hibernate-mapping package="your.domain">
<class name="MyEntity">
<id name="id" type="my-custom-id-type">
...
</id>
<class>
&types;
</hibernate-mapping>
types.xml是your.domain包中的一个资源,它包含了一个定制的第 5.2.3 节 “自定义值类型”。
这个元素包括一些可选的属性。schema和catalog属性,
指明了这个映射所连接(refer)的表所在的schema和/或catalog名称。
假若指定了这个属性,表名会加上所指定的schema和catalog的名字扩展为全限定名。假若没有指定,表名就不会使用全限定名。
default-cascade指定了未明确注明cascade属性的Java属性和
集合类Hibernate会采取什么样的默认级联风格。auto-import属性默认让我们在查询语言中可以使用
非全限定名的类名。
<hibernate-mapping
schema="schemaName"
catalog="catalogName"
default-cascade="cascade_style"
default-access="field|property|ClassName"
default-lazy="true|false"
auto-import="true|false"
package="package.name"
/>
![]() |
|
![]() |
|
![]() |
|
![]() |
|
![]() |
|
![]() |
|
![]() |
|
假若你有两个持久化类,它们的非全限定名是一样的(就是两个类的名字一样,所在的包不一样--译者注),
你应该设置auto-import="false"。如果你把一个“import过”的名字同时对应两个类,
Hibernate会抛出一个异常。
注意hibernate-mapping 元素允许你嵌套多个如上所示的
<class>映射。但是最好的做法(也许一些工具需要的)是一个
持久化类(或一个类的继承层次)对应一个映射文件,并以持久化的超类名称命名,例如:
Cat.hbm.xml,
Dog.hbm.xml,或者如果使用继承,Animal.hbm.xml。
你可以使用class元素来定义一个持久化类:
<class
name="ClassName"
table="tableName"
discriminator-value="discriminator_value"
mutable="true|false"
schema="owner"
catalog="catalog"
proxy="ProxyInterface"
dynamic-update="true|false"
dynamic-insert="true|false"
select-before-update="true|false"
polymorphism="implicit|explicit"
where="arbitrary sql where condition"
persister="PersisterClass"
batch-size="N"
optimistic-lock="none|version|dirty|all"
lazy="true|false"
entity-name="EntityName"
check="arbitrary sql check condition"
rowid="rowid"
subselect="SQL expression"
abstract="true|false"
node="element-name"
/>
![]() |
|
![]() |
|
![]() |
|
![]() |
|
![]() |
|
![]() |
|
![]() |
|
![]() |
|
![]() |
|
![]() |
|
![]() |
|
![]() |
|
![]() |
|
![]() |
|
![]() |
|
| (16) |
|
| (17) |
|
| (18) |
|
| (19) |
|
| (20) |
|
| (21) |
|
若指明的持久化类实际上是一个接口,这也是完全可以接受的。
之后你可以用元素<subclass>来指定该接口的实际实现类。
你可以持久化任何static(静态的)内部类。
你应该使用标准的类名格式来指定类名,比如:Foo$Bar。
不可变类,mutable="false"不可以被应用程序更新或者删除。
这可以让Hibernate做一些小小的性能优化。
可选的proxy属性允许延迟加载类的持久化实例。
Hibernate开始会返回实现了这个命名接口的CGLIB代理。当代理的某个方法被实际调用的时候,
真实的持久化对象才会被装载。参见下面的“用于延迟装载的代理”。
Implicit (隐式)的多态是指,如果查询时给出的是任何超类、该类实现的接口或者该类的
名字,都会返回这个类的实例;如果查询中给出的是子类的名字,则会返回子类的实例。
Explicit (显式)的多态是指,只有在查询时给出明确的该类名字时才会返回这个类的实例;
同时只有在这个<class>的定义中作为<subclass>
或者<joined-subclass>出现的子类,才会可能返回。
在大多数情况下,默认的polymorphism="implicit"都是合适的。
显式的多态在有两个不同的类映射到同一个表的时候很有用。(允许一个“轻型”的类,只包含部分表字段)。
persister属性可以让你定制这个类使用的持久化策略。
你可以指定你自己实现
org.hibernate.persister.EntityPersister的子类,你甚至可以完全从头开始编写一个
org.hibernate.persister.ClassPersister接口的实现,
比如是用储存过程调用、序列化到文件或者LDAP数据库来实现。
参阅org.hibernate.test.CustomPersister,这是一个简单的例子
(“持久化”到一个Hashtable)。
请注意dynamic-update和dynamic-insert的设置并不会继承到子类,
所以在<subclass>或者<joined-subclass>元素中可能
需要再次设置。这些设置是否能够提高效率要视情形而定。请用你的智慧决定是否使用。
使用select-before-update通常会降低性能。如果你重新连接一个脱管(detache)对象实例
到一个Session中时,它可以防止数据库不必要的触发update。
这就很有用了。
如果你打开了dynamic-update,你可以选择几种乐观锁定的策略:
version(版本检查) 检查version/timestamp字段
all(全部) 检查全部字段
dirty(脏检查)只检察修改过的字段
none(不检查)不使用乐观锁定
我们非常强烈建议你在Hibernate中使用version/timestamp字段来进行乐观锁定。
对性能来说,这是最好的选择,并且这也是唯一能够处理在session外进行操作的策略(例如:
在使用Session.merge()的时候)。
对Hibernate映射来说视图和表是没有区别的,这是因为它们在数据层都是透明的( 注意:一些数据库不支持视图属性,特别是更新的时候)。有时你想使用视图,但却不能在数据库 中创建它(例如:在遗留的schema中)。这样的话,你可以映射一个不可变的(immutable)并且是 只读的实体到一个给定的SQL子查询表达式:
<class name="Summary">
<subselect>
select item.name, max(bid.amount), count(*)
from item
join bid on bid.item_id = item.id
group by item.name
</subselect>
<synchronize table="item"/>
<synchronize table="bid"/>
<id name="name"/>
...
</class>
定义这个实体用到的表为同步(synchronize),确保自动刷新(auto-flush)正确执行,
并且依赖原实体的查询不会返回过期数据。<subselect>在属性元素
和一个嵌套映射元素中都可见。
被映射的类必须定义对应数据库表主键字段。大多数类有一个JavaBeans风格的属性,
为每一个实例包含唯一的标识。<id> 元素定义了该属性到数据库表主键字段的映射。
<id
name="propertyName"
type="typename"
column="column_name"
unsaved-value="null|any|none|undefined|id_value"
access="field|property|ClassName"
node="element-name|@attribute-name|element/@attribute|.">
<generator class="generatorClass"/>
</id>
![]() |
|
![]() |
|
![]() |
|
![]() |
|
![]() |
|
如果 name属性不存在,会认为这个类没有标识属性。
unsaved-value 属性在Hibernate3中几乎不再需要。
还有一个另外的<composite-id>定义可以访问旧式的多主键数据。
我们强烈不建议使用这种方式。
可选的<generator>子元素是一个Java类的名字,
用来为该持久化类的实例生成唯一的标识。如果这个生成器实例需要某些配置值或者初始化参数,
用<param>元素来传递。
<id name="id" type="long" column="cat_id">
<generator class="org.hibernate.id.TableHiLoGenerator">
<param name="table">uid_table</param>
<param name="column">next_hi_value_column</param>
</generator>
</id>
所有的生成器都实现org.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_key 和next_hi)作为高位值的来源。
高/低位算法生成的标识符只在一个特定的数据库中是唯一的。
seqhilo
使用一个高/低位算法来高效的生成long, short
或者 int类型的标识符,给定一个数据库序列(sequence)的名字。
uuid用一个128-bit的UUID算法生成字符串类型的标识符, 这在一个网络中是唯一的(使用了IP地址)。UUID被编码为一个32位16进制数字的字符串。
guid在MS SQL Server 和 MySQL 中使用数据库生成的GUID字符串。
native
根据底层数据库的能力选择identity, sequence
或者hilo中的一个。
assigned
让应用程序在save()之前为对象分配一个标示符。这是
<generator>元素没有指定时的默认生成策略。
select通过数据库触发器选择一些唯一主键的行并返回主键值来分配一个主键。
foreign
使用另外一个相关联的对象的标识符。通常和<one-to-one>联合起来使用。
sequence-identity一种特别的序列生成策略,使用数据库序列来生成实际值,但将它和JDBC3的getGeneratedKeys结合在一起,使得在插入语句执行的时候就返回生成的值。目前为止只有面向JDK 1.4的Oracle 10g驱动支持这一策略。注意,因为Oracle驱动程序的一个bug,这些插入语句的注释被关闭了。(原文:Note comments on these insert statements are disabled due to a bug in the Oracle drivers.)
hilo 和 seqhilo生成器给出了两种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时无法使用hilo。
当Hibernate使用JTA获取应用服务器的数据源连接时,你必须正确地配置
hibernate.transaction.manager_lookup_class。
UUID包含:IP地址,JVM的启动时间(精确到1/4秒),系统时间和一个计数器值(在JVM中唯一)。 在Java代码中不可能获得MAC地址或者内存地址,所以这已经是我们在不使用JNI的前提下的能做的最好实现了。
对于内部支持标识字段的数据库(DB2,MySQL,Sybase,MS SQL),你可以使用identity关键字生成。
对于内部支持序列的数据库(DB2,Oracle, PostgreSQL, Interbase, McKoi,SAP DB),
你可以使用sequence风格的关键字生成。
这两种方式对于插入一个新的对象都需要两次SQL查询。
<id name="id" type="long" column="person_id">
<generator class="sequence">
<param name="sequence">person_id_sequence</param>
</generator>
</id>
<id name="id" type="long" column="person_id" unsaved-value="0">
<generator class="identity"/>
</id>
对于跨平台开发,native策略会从identity,
sequence 和hilo中进行选择,选择哪一个,这取决于底层数据库的支持能力。
如果你需要应用程序分配一个标示符(而非Hibernate来生成),你可以使用assigned
生成器。这种特殊的生成器会使用已经分配给对象的标识符属性的标识符值。
这个生成器使用一个自然键(natural key,有商业意义的列-译注)作为主键,而不是使用一个代理键(
surrogate key,没有商业意义的列-译注)。这是没有指定<generator>元素时的默认行为
当选择assigned生成器时,除非有一个version或timestamp属性,或者你定义了
Interceptor.isUnsaved(),否则需要让Hiberante使用
unsaved-value="undefined",强制Hibernatet查询数据库来确定一个实例是瞬时的(transient)
还是脱管的(detached)。
仅仅用于遗留的schema中 (Hibernate不能使用触发器生成DDL)。
<id name="id" type="long" column="person_id">
<generator class="select">
<param name="key">socialSecurityNumber</param>
</generator>
</id>
在上面的例子中,类定义了一个命名为socialSecurityNumber的唯一值属性,
它是一个自然键(natural key),命名为person_id的代理键(surrogate key)
的值由触发器生成。
<composite-id
name="propertyName"
class="ClassName"
mapped="true|false"
access="field|property|ClassName"
node="element-name|."
>
<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()
组合关键字关联的持久状态。我们把这种方法称为embedded(嵌入式)的组合标识符,在重要的应用中不鼓励使用这种用法。
第二种方法我们称为mapped(映射式)组合标识符 (mapped composite identifier),<composite-id>元素中列出的标识属性不但在持久化类出现,还形成一个独立的标识符类。
<composite-id class="MedicareId" mapped="true">
<key-property name="medicareNumber"/>
<key-property name="dependent"/>
</composite-id>
在这个例子中,组合标识符类MedicareId和实体类都含有medicareNumber和dependent属性。标识符类必须重载equals()和hashCode()并且实现Serializable接口。这种方法的缺点是出现了明显的代码重复。
下面列出的属性是用来指定一个映射式组合标识符的:
mapped (可选, 默认为false):
指明使用一个映射式组合标识符,其包含的属性映射同时在实体类和组合标识符类中出现。
class (可选,但对映射式组合标识符必须指定):
作为组合标识符类使用的类名.
在第 8.4 节 “组件作为联合标识符(Components as composite identifiers)”一节中,我们会描述第三种方式,那就是把组合标识符实现为一个组件(component)类,这是更方便的方法。下面的属性仅对第三种方法有效:
name (可选,但对这种方法而言必须): 包含此组件标识符的组件类型的名字 (参阅第9章).
access (可选 - 默认为property):
Hibernate应该使用的访问此属性值的策略
class (可选 - 默认会用反射来自动判定属性类型
): 用来作为组合标识符的组件类的类名(参阅下一节)
第三种方式,被称为identifier component(标识符组件)是我们对几乎所有应用都推荐使用的方式。
在"一棵对象继承树对应一个表"的策略中,<discriminator>元素是必需的,
它定义了表的鉴别器字段。鉴别器字段包含标志值,用于告知持久化层应该为某个特定的行创建哪一个子类的实例。
如下这些受到限制的类型可以使用:
string, character, integer,
byte, short, boolean,
yes_no, true_false.
<discriminator
column="discriminator_column"
type="discriminator_type"
force="true|false"
insert="true|false"
formula="arbitrary sql expression"
/>
![]() |
|
![]() |
|
![]() |
|
![]() |
|
![]() |
|
鉴别器字段的实际值是根据<class>和<subclass>元素中
的discriminator-value属性得来的。
force属性仅仅在这种情况下有用的:表中包含没有被映射到持久化类的附加辨别器值。
这种情况不会经常遇到。
使用formula属性你可以定义一个SQL表达式,用来判断一个行数据的类型。
<discriminator
formula="case when CLASS_TYPE in ('a', 'b', 'c') then 0 else 1 end"
type="integer"/>
<version>元素是可选的,表明表中包含附带版本信息的数据。
这在你准备使用 长事务(long transactions)的时候特别有用。(见后)
<version
column="version_column"
name="propertyName"
type="typename"
access="field|property|ClassName"
unsaved-value="null|negative|undefined"
generated="never|always"
insert="true|false"
node="element-name|@attribute-name|element/@attribute|."
/>
![]() |
|
![]() |
|
![]() |
|
![]() |
|
![]() |
|
![]() |
|
![]() |
|
版本号必须是以下类型:long, integer,
short, timestamp或者calendar。
一个脱管(detached)实例的version或timestamp属性不能为空(null),因为Hibernate不管
unsaved-value被指定为何种策略,它将任何属性为空的version或timestamp
实例看作为瞬时(transient)实例。
避免Hibernate中的传递重附(transitive reattachment)问题的一个简单方法是
定义一个不能为空的version或timestamp属性,特别是在人们使用程序分配的标识符(assigned identifiers)
或复合主键时非常有用!
可选的<timestamp>元素指明了表中包含时间戳数据。
这用来作为版本的替代。时间戳本质上是一种对乐观锁定的一种不是特别安全的实现。当然,
有时候应用程序可能在其他方面使用时间戳。
<timestamp
column="timestamp_column"
name="propertyName"
access="field|property|ClassName"
unsaved-value="null|undefined"
source="vm|db"
generated="never|always"
node="element-name|@attribute-name|element/@attribute|."
/>
![]() |
|
![]() |
|
![]() |
|
![]() |
|
![]() |
|
![]() |
|
注意,<timestamp> 和<version type="timestamp">是等价的。并且<timestamp source="db">和<version type="dbtimestamp">是等价的。
<property>元素为类定义了一个持久化的,JavaBean风格的属性。
<property
name="propertyName"
column="column_name"
type="typename"
update="true|false"
insert="true|false"
formula="arbitrary SQL expression"
access="field|property|ClassName"
lazy="true|false"
unique="true|false"
not-null="true|false"
optimistic-lock="true|false"
generated="never|insert|always"
node="element-name|@attribute-name|element/@attribute|."
index="index_name"
unique_key="unique_key_id"
length="L"
precision="P"
scale="S"
/>
![]() |
|
![]() |
|
![]() |
|
![]() |
|
![]() |
|
![]() |
|
![]() |
|
![]() |
|
![]() |
|
![]() |
|
![]() |
|
typename可以是如下几种:
Hibernate基本类型名(比如:integer, string, character,date, timestamp,
float, binary, serializable, object, blob)。
一个Java类的名字,这个类属于一种默认基础类型
(比如: int, float,char, java.lang.String, java.util.Date, java.lang.Integer,
java.sql.Clob)。
一个可以序列化的Java类的名字。
一个自定义类型的类的名字。(比如: com.illflow.type.MyCustomType)。
如果你没有指定类型,Hibernarte会使用反射来得到这个名字的属性,以此来猜测正确的Hibernate类型。
Hibernate会按照规则2,3,4的顺序对属性读取器(getter方法)的返回类进行解释。然而,这还不够。
在某些情况下你仍然需要type属性。(比如,为了区别Hibernate.DATE
和Hibernate.TIMESTAMP,或者为了指定一个自定义类型。)
access属性用来让你控制Hibernate如何在运行时访问属性。在默认情况下,
Hibernate会使用属性的get/set方法对(pair)。如果你指明access="field",
Hibernate会忽略get/set方法对,直接使用反射来访问成员变量。你也可以指定你自己的策略,
这就需要你自己实现org.hibernate.property.PropertyAccessor接口,
再在access中设置你自定义策略类的名字。
衍生属性(derive propertie)是一个特别强大的特征。这些属性应该定义为只读,属性值在装载时计算生成。
你用一个SQL表达式生成计算的结果,它会在这个实例转载时翻译成一个SQL查询的SELECT
子查询语句。
<property name="totalPrice"
formula="( SELECT SUM (li.quantity*p.price) FROM LineItem li, Product p
WHERE li.productId = p.productId
AND li.customerId = customerId
AND li.orderNumber = orderNumber )"/>
注意,你可以使用实体自己的表,而不用为这个特别的列定义别名(
上面例子中的customerId)。同时注意,如果你不喜欢使用属性,
你可以使用嵌套的<formula>映射元素。
通过many-to-one元素,可以定义一种常见的与另一个持久化类的关联。
这种关系模型是多对一关联(实际上是一个对象引用-译注):这个表的一个外键引用目标表的
主键字段。
<many-to-one
name="propertyName"
column="column_name"
class="ClassName"
cascade="cascade_style"
fetch="join|select"
update="true|false"
insert="true|false"
property-ref="propertyNameFromAssociatedClass"
access="field|property|ClassName"
unique="true|false"
not-null="true|false"
optimistic-lock="true|false"
lazy="proxy|no-proxy|false"
not-found="ignore|exception"
entity-name="EntityName"
formula="arbitrary SQL expression"
node="element-name|@attribute-name|element/@attribute|."
embed-xml="true|false"
index="index_name"
unique_key="unique_key_id"
foreign-key="foreign_key_name"
/>
![]() |
|
![]() |
|
![]() |
|
![]() |
|
![]() |
|
![]() |
|
![]() |
|
![]() |
|
![]() |
|
![]() |
|
![]() |
|
![]() |
|
![]() |
|
![]() |
|
![]() |
|
cascade属性设置为除了none以外任何有意义的值,
它将把特定的操作传递到关联对象中。这个值就代表着Hibernate基本操作的名称,
persist, merge, delete, save-update, evict, replicate, lock, refresh,
以及特别的值delete-orphan和all,并且可以用逗号分隔符
来组合这些操作,例如,cascade="persist,merge,evict"或
cascade="all,delete-orphan"。更全面的解释请参考第 10.11 节 “传播性持久化(transitive persistence)”. 注意,单值关联 (many-to-one 和
one-to-one 关联) 不支持删除孤儿(orphan delete,删除不再被引用的值).
一个典型的简单many-to-one定义例子:
<many-to-one name="product" class="Product" column="PRODUCT_ID"/>
property-ref属性只应该用来对付遗留下来的数据库系统,
可能有外键指向对方关联表的是个非主键字段(但是应该是一个惟一关键字)的情况下。
这是一种十分丑陋的关系模型。比如说,假设Product类有一个惟一的序列号,
它并不是主键。(unique属性控制Hibernate通过SchemaExport工具进行的DDL生成。)
<property name="serialNumber" unique="true" type="string" column="SERIAL_NUMBER"/>
那么关于OrderItem 的映射可能是:
<many-to-one name="product" property-ref="serialNumber" column="PRODUCT_SERIAL_NUMBER"/>
当然,我们决不鼓励这种用法。
如果被引用的唯一主键由关联实体的多个属性组成,你应该在名称为<properties>的元素
里面映射所有关联的属性。
假若被引用的唯一主键是组件的属性,你可以指定属性路径:
<many-to-one name="owner" property-ref="identity.ssn" column="OWNER_SSN"/>
持久化对象之间一对一的关联关系是通过one-to-one元素定义的。
<one-to-one
name="propertyName"
class="ClassName"
cascade="cascade_style"
constrained="true|false"
fetch="join|select"
property-ref="propertyNameFromAssociatedClass"
access="field|property|ClassName"
formula="any SQL expression"
lazy="proxy|no-proxy|false"
entity-name="EntityName"
node="element-name|@attribute-name|element/@attribute|."
embed-xml="true|false"
foreign-key="foreign_key_name"
/>
![]() |
|
![]() |
|
![]() |
|
![]() |
|
![]() |
|
![]() |
|
![]() |
|
![]() |
|
![]() |
|
![]() |
|
有两种不同的一对一关联:
主键关联
惟一外键关联
主键关联不需要额外的表字段;如果两行是通过这种一对一关系相关联的,那么这两行就共享同样的主关键字值。所以如果你希望两个对象通过主键一对一关联,你必须确认它们被赋予同样的标识值!
比如说,对下面的Employee和Person进行主键一对一关联:
<one-to-one name="person" class="Person"/>
<one-to-one name="employee" class="Employee" constrained="true"/>
现在我们必须确保PERSON和EMPLOYEE中相关的字段是相等的。我们使用一个被成为foreign的特殊的hibernate标识符生成策略:
<class name="person" table="PERSON">
<id name="id" column="PERSON_ID">
<generator class="foreign">
<param name="property">employee</param>
</generator>
</id>
...
<one-to-one name="employee"
class="Employee"
constrained="true"/>
</class>
一个刚刚保存的Person实例被赋予和该Person的employee属性所指向的Employee实例同样的关键字值。
另一种方式是一个外键和一个惟一关键字对应,上面的Employee和Person的例子,如果使用这种关联方式,可以表达成:
<many-to-one name="person" class="Person" column="PERSON_ID" unique="true"/>
如果在Person的映射加入下面几句,这种关联就是双向的:
<one-to-one name"employee" class="Employee" property-ref="person"/>
<natural-id mutable="true|false"/>
<property ... />
<many-to-one ... />
......
</natural-id>
我们建议使用代用键(键值不具备实际意义)作为主键,我们仍然应该尝试为所有的实体采用自然的键值作为(附加——译者注)标示。自然键(natural key)是单个或组合属性,他们必须唯一且非空。如果它还是不可变的那就更理想了。在<natural-id>元素中列出自然键的属性。Hibernate会帮你生成必须的唯一键值和非空约束,你的映射会更加的明显易懂(原文是self-documenting,自我注解)。
我们强烈建议你实现equals() 和hashCode()方法,来比较实体的自然键属性。
这一映射不是为了把自然键作为主键而准备的。
mutable (可选, 默认为false):
默认情况下,自然标识属性被假定为不可变的(常量)。
<component>元素把子对象的一些元素与父类对应的表的一些字段映射起来。 然后组件可以定义它们自己的属性、组件或者集合。参见后面的“Components”一章。
<component
name="propertyName"
class="className"
insert="true|false"
update="true|false"
access="field|property|ClassName"
lazy="true|false"
optimistic-lock="true|false"
unique="true|false"
node="element-name|."
>
<property ...../>
<many-to-one .... />
........
</component>
![]() |
|
![]() |
|
![]() |
|
![]() |
|
![]() |
|
![]() |
|
![]() |
|
![]() |
|
其<property>子标签为子类的一些属性与表字段之间建立映射。
<component>元素允许加入一个<parent>子元素,在组件类内部就可以有一个指向其容器的实体的反向引用。
<dynamic-component>元素允许把一个Map映射为组件,其属性名对应map的键值。
参见第 8.5 节 “动态组件 (Dynamic components)”.
<properties> 元素允许定义一个命名的逻辑分组(grouping)包含一个类中的多个属性。
这个元素最重要的用处是允许多个属性的组合作为property-ref的目标(target)。
这也是定义多字段唯一约束的一种方便途径。
<properties
name="logicalName"
insert="true|false"
update="true|false"
optimistic-lock="true|false"
unique="true|false"
>
<property ...../>
<many-to-one .... />
........
</properties>
![]() |
|
![]() |
|
![]() |
|
![]() |
|
![]() |
|
例如,如果我们有如下的<properties>映射:
<class name="Person">
<id name="personNumber"/>
...
<properties name="name"
unique="true" update="false">
<property name="firstName"/>
<property name="initial"/>
<property name="lastName"/>
</properties>
</class>
然后,我们可能有一些遗留的数据关联,引用 Person表的这个唯一键,而不是主键。
<many-to-one name="person"
class="Person" property-ref="name">
<column name="firstName"/>
<column name="initial"/>
<column name="lastName"/>
</many-to-one>
我们并不推荐这样使用,除非在映射遗留数据的情况下。
最后,多态持久化需要为父类的每个子类都进行定义。对于“每一棵类继承树对应一个表”的策略来说,就需要使用<subclass>定义。
<subclass
name="ClassName"
discriminator-value="discriminator_value"
proxy="ProxyInterface"
lazy="true|false"
dynamic-update="true|false"
dynamic-insert="true|false"
entity-name="EntityName"
node="element-name"
extends="SuperclassName">
<property .... />
.....
</subclass>
![]() |
|
![]() |
|
![]() |
|
![]() |
|
每个子类都应该定义它自己的持久化属性和子类。
<version> 和<id> 属性可以从根父类继承下来。在一棵继承树上的每个子类都必须定义一个唯一的discriminator-value。如果没有指定,就会使用Java类的全限定名。
更多关于继承映射的信息, 参考 第 9 章 继承映射(Inheritance Mappings)章节.
此外,每个子类可能被映射到他自己的表中(每个子类一个表的策略)。被继承的状态通过和超类的表关联得到。我们使用<joined-subclass>元素。
<joined-subclass
name="ClassName"
table="tablename"
proxy="ProxyInterface"
lazy="true|false"
dynamic-update="true|false"
dynamic-insert="true|false"
schema="schema"
catalog="catalog"
extends="SuperclassName"
persister="ClassName"
subselect="SQL expression"
entity-name="EntityName"
node="element-name">
<key .... >
<property .... />
.....
</joined-subclass>
![]() |
|
![]() |
|
![]() |
|
![]() |
|
这种映射策略不需要指定辨别标志(discriminator)字段。但是,每一个子类都必须使用<key>元素指定一个表字段来持有对象的标识符。本章开始的映射可以被用如下方式重写:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.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>
更多关于继承映射的信息,参考第 9 章 继承映射(Inheritance Mappings)。
第三种选择是仅仅映射类继承树中具体类部分到表中(每个具体类一张表的策略)。其中,每张表定义了类的所有持久化状态,包括继承的状态。在 Hibernate 中,并不需要完全显式地映射这样的继承树。你可以简单地使用单独的<class>定义映射每个类。然而,如果你想使用多态关联(例如,一个对类继承树中超类的关联),你需要使用<union-subclass>映射。
<union-subclass
name="ClassName"
table="tablename"
proxy="ProxyInterface"
lazy="true|false"
dynamic-update="true|false"
dynamic-insert="true|false"
schema="schema"
catalog="catalog"
extends="SuperclassName"
abstract="true|false"
persister="ClassName"
subselect="SQL expression"
entity-name="EntityName"
node="element-name">
<property .... />
.....
</union-subclass>
![]() |
|
![]() |
|
![]() |
|
![]() |
|
这种映射策略不需要指定辨别标志(discriminator)字段。
更多关于继承映射的信息,参考第 9 章 继承映射(Inheritance Mappings)。
使用 <join> 元素,假若在表之间存在一对一关联,可以将一个类的属性映射到多张表中。
<join
table="tablename"
schema="owner"
catalog="catalog"
fetch="join|select"
inverse="true|false"
optional="true|false">
<key ... />
<property ... />
...
</join>
![]() |
|
![]() |
|
![]() |
|
![]() |
|
![]() |
|
![]() |
|
例如,一个人(person)的地址(address)信息可以被映射到单独的表中(并保留所有属性的值类型语义):
<class name="Person"
table="PERSON">
<id name="id" column="PERSON_ID">...</id>
<join table="ADDRESS">
<key column="ADDRESS_ID"/>
<property name="address"/>
<property name="zip"/>
<property name="country"/>
</join>
...
此特性常常对遗留数据模型有用,我们推荐表个数比类个数少,以及细粒度的领域模型。然而,在单独的继承树上切换继承映射策略是有用的,后面会解释这点。
我们目前已经见到过<key>元素多次了。 这个元素在父映射元素定义了对新表的连接,并且在被连接表中定义了一个外键引用原表的主键的情况下经常使用。
<key
column="columnname"
on-delete="noaction|cascade"
property-ref="propertyName"
not-null="true|false"
update="true|false"
unique="true|false"
/>
![]() |
|
![]() |
|
![]() |
|
![]() |
|
![]() |
|
![]() |
|
对那些看重删除性能的系统,我们推荐所有的键都应该定义为on-delete="cascade",这样 Hibernate 将使用数据库级的ON CASCADE DELETE约束,而不是多个DELETE语句。 注意,这个特性会绕过 Hibernate 通常对版本数据(versioned data)采用的乐观锁策略。
not-null 和 update 属性在映射单向一对多关联的时候有用。如果你映射一个单向一对多关联到非空的(non-nullable)外键,你必须 用<key not-null="true">定义此键字段。
任何接受column属性的映射元素都可以选择接受<column> 子元素。同样的,formula子元素也可以替换<formula>属性。
<column
name="column_name"
length="N"
precision="N"
scale="N"
not-null="true|false"
unique="true|false"
unique-key="multicolumn_unique_key_name"
index="index_name"
sql-type="sql_type_name"
check="SQL expression"
default="SQL expression"/>
<formula>SQL expression</formula>
column 和 formula 属性甚至可以在同一个属性或关联映射中被合并来表达,例如,一些奇异的连接条件。
<many-to-one name="homeAddress" class="Address"
insert="false" update="false">
<column name="person_id" not-null="true" length="10"/>
<formula>'MAILING'</formula>
</many-to-one>
假设你的应用程序有两个同样名字的持久化类,但是你不想在Hibernate查询中使用他们的全限定名。除了依赖auto-import="true"以外,类也可以被显式地“import(引用)”。你甚至可以引用没有被明确映射的类和接口。
<import class="java.lang.Object" rename="Universe"/>
<import
class="ClassName"
rename="ShortName"
/>
![]() |
|
![]() |
|
这是属性映射的又一种类型。<any> 映射元素定义了一种从多个表到类的多态关联。这种类型的映射常常需要多于一个字段。第一个字段持有被关联实体的类型,其他的字段持有标识符。对这种类型的关联来说,不可能指定一个外键约束,所以这当然不是映射(多态)关联的通常的方式。你只应该在非常特殊的情况下使用它(比如,审计log,用户会话数据等等)。
meta-type 属性使得应用程序能指定一个将数据库字段的值映射到持久化类的自定义类型。这个持久化类包含有用id-type指定的标识符属性。
你必须指定从meta-type的值到类名的映射。
<any name="being" 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"
id-type="idtypename"
meta-type="metatypename"
cascade="cascade_style"
access="field|property|ClassName"
optimistic-lock="true|false"
>
<meta-value ... />
<meta-value ... />
.....
<column .... />
<column .... />
.....
</any>
![]() |
|
![]() |
|
![]() |
|
![]() |
|
![]() |
|
![]() |
|