Hibernate.orgCommunity Documentation

第 10 章 继承映射(Inheritance Mapping)

10.1. 三种策略
10.1.1. 每个类分层结构一张表(Table per class hierarchy)
10.1.2. 每个子类一张表(Table per subclass)
10.1.3. 每个子类一张表(Table per subclass),使用辨别标志(Discriminator)
10.1.4. 混合使用“每个类分层结构一张表”和“每个子类一张表”
10.1.5. 每个具体类一张表(Table per concrete class)
10.1.6. 每个具体类一张表,使用隐式多态
10.1.7. 隐式多态和其他继承映射混合使用
10.2. 限制

Hibernate 支持三种基本的继承映射策略:

此外,Hibernate 还支持第四种稍有不同的多态映射策略:

对于同一个继承层次内的不同分支,可以采用不同的映射策略,然后用隐式多 态来完成跨越整个层次的多态。但是在同一个 <class> 根元素下,Hibernate 不支持混合了元素 <subclass><joined-subclass><union-subclass> 的映射。在同一个 <class> 元素下,可以混合使用“每个类分层结构一张表”(table per hierarchy)和“每个子类一张表”(table per subclass) 这两种映射策略,这是通过结合元素 <subclass><join> 来实现的(见后)。

在多个映射文件中,可以直接在 hibernate-mapping 根下定义 subclassunion-subclassjoined-subclass。也就是说,你可以仅加入一个新的映射文件来扩展类层次。你必须在 subclass 的映射中指明 extends 属性,给出一个之前定义的超类的名字。注意,在以前,这一功能对映射文件的顺序有严格的要求,从 Hibernate 3 开始,使用 extends 关键字的时侯,对映射文件的顺序不再有要求;但在每个映射文件里,超类必须在子类之前定义。



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

注意,对“每个子类一张表”的映射策略,Hibernate 的实现不需要辨别字段,而其他的对象/关系映射工具使用了一种不同于Hibernate的实现方法,该方法要求在超类表中有一个类型辨别字段(type discriminator column)。Hibernate 采用的方法更难实现,但从关系(数据库)的角度来看,按理说它更正确。若你愿意使用带有辨别字段的“每个子类一张表”的策略,你可以结合使用 <subclass><join>,如下所示:


<class name="Payment" table="PAYMENT">
    <id name="id" type="long" column="PAYMENT_ID">
        <generator class="native"/>
    </id>
    <discriminator column="PAYMENT_TYPE" type="string"/>
    <property name="amount" column="AMOUNT"/>
    ...
    <subclass name="CreditCardPayment" discriminator-value="CREDIT">
        <join table="CREDIT_PAYMENT">
            <key column="PAYMENT_ID"/>
            <property name="creditCardType" column="CCTYPE"/>
            ...
        </join>
    </subclass>
    <subclass name="CashPayment" discriminator-value="CASH">
        <join table="CASH_PAYMENT">
            <key column="PAYMENT_ID"/>
            ...
        </join>
    </subclass>
    <subclass name="ChequePayment" discriminator-value="CHEQUE">
        <join table="CHEQUE_PAYMENT" fetch="select">
            <key column="PAYMENT_ID"/>
            ...
        </join>
    </subclass>
</class
>

可选的声明 fetch="select",是用来告诉 Hibernate,在查询超类时,不要使用外部连接(outer join)来抓取子类 ChequePayment 的数据。

对于“每个具体类一张表”的映射策略,可以采用两种方法。第一种方法是使用 <union-subclass>


<class name="Payment">
    <id name="id" type="long" column="PAYMENT_ID">
        <generator class="sequence"/>
    </id>
    <property name="amount" column="AMOUNT"/>
    ...
    <union-subclass name="CreditCardPayment" table="CREDIT_PAYMENT">
        <property name="creditCardType" column="CCTYPE"/>
        ...
    </union-subclass>
    <union-subclass name="CashPayment" table="CASH_PAYMENT">
        ...
    </union-subclass>
    <union-subclass name="ChequePayment" table="CHEQUE_PAYMENT">
        ...
    </union-subclass>
</class
>

这里涉及三张与子类相关的表。每张表为对应类的所有属性(包括从超类继承的属性)定义相应字段。

这种方式的局限在于,如果一个属性在超类中做了映射,其字段名必须与所有子类表中定义的相同。(我们可能会在 Hibernate 的后续发布版本中放宽此限制。)不允许在联合子类(union subclass)的继承层次中使用标识生成器策略(identity generator strategy),实际上,主键的种子(primary key seed)不得不为同一继承层次中的全部被联合子类所共用。

假若超类是抽象类,请使用 abstract="true"。当然,假若它不是抽象的,需要一个额外的表(上面的例子中,默认是 PAYMENT),来保存超类的实例。

另一种可供选择的方法是采用隐式多态:


<class name="CreditCardPayment" table="CREDIT_PAYMENT">
    <id name="id" type="long" column="CREDIT_PAYMENT_ID">
        <generator class="native"/>
    </id>
    <property name="amount" column="CREDIT_AMOUNT"/>
    ...
</class>

<class name="CashPayment" table="CASH_PAYMENT">
    <id name="id" type="long" column="CASH_PAYMENT_ID">
        <generator class="native"/>
    </id>
    <property name="amount" column="CASH_AMOUNT"/>
    ...
</class>

<class name="ChequePayment" table="CHEQUE_PAYMENT">
    <id name="id" type="long" column="CHEQUE_PAYMENT_ID">
        <generator class="native"/>
    </id>
    <property name="amount" column="CHEQUE_AMOUNT"/>
    ...
</class
>

请注意,这里没有显性地提及 Payment 接口。Payment 的属性映射到每个子类。如果你想避免重复,请考虑使用 XML 实体(如:DOCTYPE 声明里的 [ <!ENTITY allproperties SYSTEM "allproperties.xml"> ] 和映射里的 &allproperties;)。

这种方法的缺陷在于,在 Hibernate 执行多态查询时(polymorphic queries)无法生成带 UNION 的 SQL 语句。

对于这种映射策略而言,通常用 <any> 来实现到 Payment 的多态关联映射。


<any name="payment" meta-type="string" id-type="long">
    <meta-value value="CREDIT" class="CreditCardPayment"/>
    <meta-value value="CASH" class="CashPayment"/>
    <meta-value value="CHEQUE" class="ChequePayment"/>
    <column name="PAYMENT_CLASS"/>
    <column name="PAYMENT_ID"/>
</any
>

对这一映射还有一点需要注意。因为每个子类都在各自独立的元素 <class> 中映射(并且 Payment 只是一个接口),每个子类可以很容易的成为另一个继承体系中的一部分!(你仍然可以对接口 Payment 使用多态查询。)


<class name="CreditCardPayment" table="CREDIT_PAYMENT">
    <id name="id" type="long" column="CREDIT_PAYMENT_ID">
        <generator class="native"/>
    </id>
    <discriminator column="CREDIT_CARD" type="string"/>
    <property name="amount" column="CREDIT_AMOUNT"/>
    ...
    <subclass name="MasterCardPayment" discriminator-value="MDC"/>
    <subclass name="VisaPayment" discriminator-value="VISA"/>
</class>

<class name="NonelectronicTransaction" table="NONELECTRONIC_TXN">
    <id name="id" type="long" column="TXN_ID">
        <generator class="native"/>
    </id>
    ...
    <joined-subclass name="CashPayment" table="CASH_PAYMENT">
        <key column="PAYMENT_ID"/>
        <property name="amount" column="CASH_AMOUNT"/>
        ...
    </joined-subclass>
    <joined-subclass name="ChequePayment" table="CHEQUE_PAYMENT">
        <key column="PAYMENT_ID"/>
        <property name="amount" column="CHEQUE_AMOUNT"/>
        ...
    </joined-subclass>
</class
>

我们还是没有明确的提到 Payment。如果我们针对接口 Payment 执行查询 — 如 from Payment — Hibernate 自动返回 CreditCardPayment(和它的子类,因为 它们也实现了接口 Payment)、CashPaymentChequepayment 的实例,但不返回 NonelectronicTransaction 的实例。

对“每个具体类映射一张表”(table per concrete-class)的映射策略而言,隐式多态的方式有一定的限制。而 <union-subclass> 映射的限制则没有那么严格。

下面表格中列出了在 Hibernte 中“每个具体类一张表”的策略和隐式多态的限制。