Hibernate.org Community Documentation

- 上一页
- 下一页

## 第 9 章 组件（Component）映射

组件（Component）这个概念在 Hibernate 中几处不同的地方为了不同的目的被重复使用。

## 9.1. 依赖对象（Dependent objects）

组件（Component）是一个被包含的对象，在持久化的过程中，它被当作值类型，而并非一个实体的引用。在这篇文档中，组件这一术语指的是面向对象的合成概念（而并不是系统构架层次上的组件的概念）。举个例子，你对人（Person）这个概念可以像下面这样来建模：

    public class Person {

        private java.util.Date birthday;

        private Name name;

        private String key;

        public String getKey() {

            return key;

        }

        private void setKey(String key) {

            this.key=key;

        }

        public java.util.Date getBirthday() {

            return birthday;

        }

        public void setBirthday(java.util.Date birthday) {

            this.birthday = birthday;

        }

        public Name getName() {

            return name;

        }

        public void setName(Name name) {

            this.name = name;

        }

        ......

        ......

    }

    public class Name {

        char initial;

        String first;

        String last;

        public String getFirst() {

            return first;

        }

        void setFirst(String first) {

            this.first = first;

        }

        public String getLast() {

            return last;

        }

        void setLast(String last) {

            this.last = last;

        }

        public char getInitial() {

            return initial;

        }

        void setInitial(char initial) {

            this.initial = initial;

        }

    }

在持久化的过程中，`姓名（Name）`可以作为`人（Person）`的一个组件。需要注意的是：你应该为`姓名`的持久化属性定义 getter 和 setter 方法，但是你不需要实现任何的接口或申明标识符字段。

以下是这个例子的 Hibernate 映射文件：

    <class name="eg.Person" table="person">

        <id name="Key" column="pid" type="string">

            <generator class="uuid"/>

        </id>

        <property name="birthday" type="date"/>

        <component name="Name" class="eg.Name"

    > <!-- class attribute optional -->

            <property name="initial"/>

            <property name="first"/>

            <property name="last"/>

        </component>

    </class

    >

人员（Person）表中将包括 `pid`，`birthday`，`initial`，`first`和 `last` 等字段。

就像所有的值类型一样，组件不支持共享引用。换句话说，两个人可能重名，但是两个 Person 对象应该包含两个独立的 Name 对象，只不过这两个 Name 对象具有“同样”的值。组件的值可以为空，其定义如下。 每当 Hibernate 重新加载一个包含组件的对象，如果该组件的所有字段为空，Hibernate 将假定整个组件为空。在大多数情况下，这样假定应该是没有问题的。

组件的属性可以是任意一种 Hibernate 类型（包括集合，多对多关联，以及其它组件等等）。嵌套组件不应该被当作一种特殊的应用（Nested components should not be considered an exotic usage）。Hibernate 倾向于支持细颗粒度的（fine-grained）对象模型。

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

    <class name="eg.Person" table="person">

        <id name="Key" column="pid" type="string">

            <generator class="uuid"/>

        </id>

        <property name="birthday" type="date"/>

        <component name="Name" class="eg.Name" unique="true">

            <parent name="namedPerson"/> <!-- reference back to the Person -->

            <property name="initial"/>

            <property name="first"/>

            <property name="last"/>

        </component>

    </class

    >

## 9.2. 在集合中出现的依赖对象（Collections of dependent objects）

Hibernate 支持组件的集合（例如：一个元素是姓名 `Name` 这种类型的数组）。你可以使用 `<composite-element>` 标签替代 `<element>` 标签来定义你的组件集合。

    <set name="someNames" table="some_names" lazy="true">

        <key column="id"/>

        <composite-element class="eg.Name"

    > <!-- class attribute required -->

            <property name="initial"/>

            <property name="first"/>

            <property name="last"/>

        </composite-element>

    </set

    >

## 重要

注意，如果你定义的 Set 包含组合元素（composite-element），正确地实现 `equals()` 和 `hashCode()` 是非常重要的。

组合元素可以包含组件，但是不能包含集合。如果你的组合元素自身包含组件，你必须使用 `<nested-composite-element>` 标签。这是一个相当特殊的案例 — 在一个组件的集合里，那些组件本身又可以包含其他的组件。这个时候你就应该考虑一下使用 one-to-many 关联是否会更恰当。尝试对这个组合元素重新建模为一个实体 — 但是需要注意的是，虽然 Java 模型和重新建模前是一样的，关系模型和持久性语义会有细微的变化。

请注意如果你使用 `<set>` 标签，一个组合元素的映射不支持可能为空的属性. 当删除对象时，Hibernate 必须使用每一个字段的值来确定一条记录（在组合元素表中，没有单独的关键字段），如果有为 null 的字段，这样做就不可能了。你必须作出一个选择，要么在组合元素中使用不能为空的属性，要么选择使用 `<list>`，`<map>`，`<bag>` 或者 `<idbag>` 而不是 `<set>`。

组合元素有个特别的用法是它可以包含一个`<many-to-one>`元素。类似这样的映射允许你将一个 many-to-many 关联表的额外字段映射为组合元素类。接下来的的例子是从 `Order` 到 `Item` 的一个多对多的关联关系，关联属性是 `purchaseDate`，`price` 和 `quantity` 。

    <class name="eg.Order" .... >

        ....

        <set name="purchasedItems" table="purchase_items" lazy="true">

            <key column="order_id">

            <composite-element class="eg.Purchase">

                <property name="purchaseDate"/>

                <property name="price"/>

                <property name="quantity"/>

                <many-to-one name="item" class="eg.Item"/> <!-- class attribute is optional -->

            </composite-element>

        </set>

    </class

    >

当然，当你定义 Item 时，你无法引用这些 purchase，因此你无法实现双向关联查询。记住组件是值类型，并且不允许共享引用。某一个特定的 `Purchase` 可以放在 `Order` 的集合中，但它不能同时被 `Item` 所引用。

其实组合元素的这个用法可以扩展到三重或多重关联:

    <class name="eg.Order" .... >

        ....

        <set name="purchasedItems" table="purchase_items" lazy="true">

            <key column="order_id">

            <composite-element class="eg.OrderLine">

                <many-to-one name="purchaseDetails class="eg.Purchase"/>

                <many-to-one name="item" class="eg.Item"/>

            </composite-element>

        </set>

    </class

    >

在查询中，表达组合元素的语法和关联到其他实体的语法是一样的。

## 9.3. 组件作为 Map 的索引（Components as Map indices ）

`<composite-map-key>` 元素允许你映射一个组件类作为一个 `Map` 的 key，前提是你必须正确的在这个类中重写了 `hashCode()` 和 `equals()` 方法。

## 9.4. 组件作为联合标识符（Components as composite identifiers）

你可以使用一个组件作为一个实体类的标识符。你的组件类必须满足以下要求：

- 它必须实现 `java.io.Serializable` 接口

- 它必须重新实现 `equals()` 和 `hashCode()` 方法，始终和组合关键字在数据库中的概念保持一致

## 注意

注意：在 Hibernate3 中，第二个要求并非是 Hibernate 强制必须的。但最好这样做。

你不能使用一个 `IdentifierGenerator` 产生组合关键字。一个应用程序必须分配它自己的标识符。

使用 `<composite-id>` 标签（并且内嵌 `<key-property>` 元素）代替通常的 `<id>` 标签。比如，`OrderLine` 类具有一个主键，这个主键依赖于 `Order` 的（联合）主键。

    <class name="OrderLine">

        <composite-id name="id" class="OrderLineId">

            <key-property name="lineId"/>

            <key-property name="orderId"/>

            <key-property name="customerId"/>

        </composite-id>

        <property name="name"/>

        <many-to-one name="order" class="Order"

                insert="false" update="false">

            <column name="orderId"/>

            <column name="customerId"/>

        </many-to-one>

        ....

    </class

    >

现在，任何指向 `OrderLine` 的外键都是复合的。在你的映射文件中，必须为其他类也这样声明。例如，一个指向 `OrderLine` 的关联可能被这样映射：

    <many-to-one name="orderLine" class="OrderLine">

    <!-- the "class" attribute is optional, as usual -->

        <column name="lineId"/>

        <column name="orderId"/>

        <column name="customerId"/>

    </many-to-one

    >

## 提示

注意在各个地方 `column` 标签都是 `column` 属性的替代写法。使用 `column` 元素只是给出一个更详细的选项，在使用 `hbm2ddl` 时会更有用。

指向 `OrderLine` 的`多对多`关联也使用联合外键：

    <set name="undeliveredOrderLines">

        <key column name="warehouseId"/>

        <many-to-many class="OrderLine">

            <column name="lineId"/>

            <column name="orderId"/>

            <column name="customerId"/>

        </many-to-many>

    </set

    >

在 `Order` 中，`OrderLine` 的集合则是这样：

    <set name="orderLines" inverse="true">

        <key>

            <column name="orderId"/>

            <column name="customerId"/>

        </key>

        <one-to-many class="OrderLine"/>

    </set

    >

与通常一样，`<one-to-many>` 元素不声明任何列。

假若 `OrderLine` 本身拥有一个集合，它也具有组合外键。

    <class name="OrderLine">

        ....

        ....

        <list name="deliveryAttempts">

            <key

    >   <!-- a collection inherits the composite key type -->

                <column name="lineId"/>

                <column name="orderId"/>

                <column name="customerId"/>

            </key>

            <list-index column="attemptId" base="1"/>

            <composite-element class="DeliveryAttempt">

                ...

            </composite-element>

        </set>

    </class

    >

## 9.5. 动态组件（Dynamic components）

你甚至可以映射 `Map` 类型的属性：

    <dynamic-component name="userAttributes">

        <property name="foo" column="FOO" type="string"/>

        <property name="bar" column="BAR" type="integer"/>

        <many-to-one name="baz" class="Baz" column="BAZ_ID"/>

    </dynamic-component

    >

从 `<dynamic-component>` 映射的语义上来讲，它和 `<component>` 是相同的。这种映射类型的优点在于通过修改映射文件，就可以具有在部署时检测真实属性的能力。利用一个 DOM 解析器，也可以在程序运行时操作映射文件。更好的是，你可以通过 `Configuration` 对象来访问（或者修改）Hibernate 的运行时元模型。

版权 © 2004 Red Hat, Inc.

- 上一页第 8 章 关联关系映射
- 上一级
- 起始页
- 下一页第 10 章 继承映射（Inheritance Mapping）