Chapter 16. 继承映射(Inheritance Mappings)

16.1. 三种策略

Hibernate支持三种不同的基本继承映射策略。

  • 每棵类继承树使用一个表(table per class hierarchy)

  • 每个子类一个表(table per subclass)

  • 每个具体类一个表(table per concrete class)(有一些限制)

甚至在一棵继承关系书中对不同的分支使用不同的映射策略也是可能的。但是和“每个具体类一个表”的映射有一样的限制。Hibernate不支持把<subclass>映射与<joined-subclass>在同一个<class> 元素中混合使用。

假设我们有一个Payment接口,有不同的实现:CreditCardPayment, CashPayment, ChequePayment。“继承数共享一个表”的映射是这样的:

<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">
        ...
    </subclass>
    <subclass name="CashPayment" discriminator-value="CASH">
        ...
    </subclass>
    <subclass name="ChequePayment" discriminator-value="CHEQUE">
        ...
    </subclass>
</class>

只需要一个表。这种映射策略由一个大限制:子类定义的字段不能有NOT NULL限制。

“每个子类一个表”的映射是这样的:

<class name="Payment" table="PAYMENT">
    <id name="id" type="long" column="PAYMENT_ID">
        <generator class="native"/>
    </id>
    <property name="amount" column="AMOUNT"/>
    ...
    <joined-subclass name="CreditCardPayment" table="CREDIT_PAYMENT">
        <key column="PAYMENT_ID"/>
        ...
    </subclass>
    <joined-subclass name="CashPayment" table="CASH_PAYMENT">
        <key column="PAYMENT_ID"/>
        ...
    </subclass>
    <joined-subclass name="ChequePayment" table="CHEQUE_PAYMENT">
        <key column="PAYMENT_ID"/>
        ...
    </subclass>
</class>

需要四个表。三个子类表通过主键和超类表关联(所以实际上关系模型是一对一关联)。

注意Hibernate的“每子类一表”的实现并不需要一个特别的辨认字段。其他的对象/关系数据库映射工具使用另一种“每子类一表”实现,需要在超类表中有一个类型辨认字段。Hibernate的这种实现更加困难,但是从关系(数据库)的角度来看,这样做更加正确。

对这两种映射策略来说,指向Payment的关联是使用<many-to-one>进行映射的。

<many-to-one name="payment"
    column="PAYMENT"
    class="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接口。我们用Hibernate的隐含多形(implicit polymorphism)机制代替。也要注意Payment的属性在三个字类中都进行了映射。

这种情形下,与Payment关联的多形关联被映射为<any>

<any name="payment" 
        meta-type="class"
        id-type="long">
    <column name="PAYMENT_CLASS"/>
    <column name="PAYMENT_ID"/>
</any>

如果我们定义UserTypemeta-type来根据不同的标识字符串映射到Payment,事情会更好一些。

<any name="payment" 
        meta-type="PaymentMetaType"
        id-type="long">
    <column name="PAYMENT_TYPE"/> <!-- CREDIT, CASH or CHEQUE -->
    <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的实例。

16.2. 限制

Hibernate假设关联严格的和一个外键字段相映射。如果一个外键具有多个关联,也是可以容忍的(你可能需要指定inverse="true"或者insert="false" update="false"),但是你不能为多重外键指定任何映射的关联。这意味着:

  • 当更改一个关联的时候,永远是更新的同一个外键

  • 当一个关联是延迟抓取(fetched lazily)的时候,只需要用一次数据库查询

  • 当一个关联是提前抓取(fetched eagerly)的时候,使用一次outer join即可

特别要指出的是,使用“每个具体类一个表”的策略来实行多形的一对多关联是不支持的。(抓取这样的关联需要多次查询或者多次join。)

下面的表格列出了Hibernte中,“每个具体类一个表”策略与隐含多形机制的限制。

Table 16.1. 缓存提供者(Cache Providers)

继承策略(Inheritance strategy)多形多对一多形一对一多形一对多多形多对多多形 load()/get()多形查询多形连接(join)Outer join 抓取
每继承树一表<many-to-one><one-to-one><one-to-many><many-to-many>s.get(Payment.class, id)from Payment pfrom Order o join o.payment p支持
每子类一表<many-to-one><one-to-one><one-to-many><many-to-many>s.get(Payment.class, id)from Payment pfrom Order o join o.payment p支持
每类一表(隐含多形)<any>不支持不支持<many-to-any>use a queryfrom Payment p不支持不支持