第9章 継承マッピング

9.1. 3つの戦略

Hibernateは3つの基本的な継承のマッピング戦略をサポートします。

  • クラス階層ごとのテーブル(table-per-class-hierarchy)

  • サブクラスごとのテーブル(table-per-subclass)

  • 具象クラスごとのテーブル(table-per-concrete-class)

加えて4つ目に、Hibernateはわずかに異なる性質を持ったポリモーフィズムをサポートします。

  • 暗黙的ポリモーフィズム

同一の継承階層の異なるブランチに対して異なるマッピング戦略を使うことができます。 その場合には全体の階層に渡るポリモーフィズムを実現するために暗黙的ポリモーフィズムを使用します。 しかし、Hibernateは同じルート <class> 要素内で <subclass> マッピング、<joined-subclass> マッピング、 <union-subclass> マッピングの同時使用をサポートしていません。 <subclass> 要素と <join> 要素を組み合わせることで、 同一 <class> 要素内での table-per-hierarchy 戦略と table-per-subclass 戦略の同時使用は可能です。次の例を見てください。

subclass, union-subclassjoined-subclass マッピングを複数のマッピングドキュメントに定義することが出来、 hibernate-mapping の直下に配置します。 これは新しいマッピングファイルを追加するだけで、クラス階層を拡張できるということです。 あらかじめマップしたスーパークラスを指定して、サブクラスマッピングに extends 属性を記述しなければなりません。 注意:この特徴により、以前はマッピング・ドキュメントの順番が重要でした。 Hibernate3からは、extendsキーワードを使う場合、マッピングドキュメントの順番は問題になりません。 1つのマッピングファイル内で順番付けを行うときは、 依然として、サブクラスを定義する前にスーパークラスを定義する必要があります。)

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

9.1.1. クラス階層ごとのテーブル(table-per-class-hierarchy)

例えば、インターフェイス Payment と、それを実装した CreditCardPaymentCashPaymentChequePayment があるとします。階層ごとのテーブルマッピングは 以下のようになります。

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

ちょうど一つのテーブルが必要です。 このマッピング戦略には一つ大きな制限があります。 CCTYPE のような、サブクラスで宣言されたカラムは NOT NULL 制約を持てません。

9.1.2. サブクラスごとのテーブル(table-per-subclass)

table-per-subclass マッピングは以下のようになります。

<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"/>
        <property name="creditCardType" column="CCTYPE"/>
        ...
    </joined-subclass>
    <joined-subclass name="CashPayment" table="CASH_PAYMENT">
        <key column="PAYMENT_ID"/>
        ...
    </joined-subclass>
    <joined-subclass name="ChequePayment" table="CHEQUE_PAYMENT">
        <key column="PAYMENT_ID"/>
        ...
    </joined-subclass>
</class>

このマッピングには4つのテーブルが必要です。3つのサブクラステーブルは スーパークラステーブルとの関連を示す主キーを持っています (実際、関係モデル上は一対一関連です)。

9.1.3. 弁別子を用いた table-per-subclass

Hibernateの table-per-subclass 実装は、discriminatorカラムを必要としないことを覚えておいてください。 Hibernate以外のO/Rマッパーは、table-per-subclass に異なる実装を用います。 それは、スーパークラスのテーブルにタイプdiscriminatorカラムを必要とします。 このアプローチは実装が困難になりますが、関係の視点から見ると、より正確なものです。 table-per-subclass 戦略でdiscriminatorカラムを使いたければ、 <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" 宣言は、 スーパークラスのクエリ実行時に外部結合を使って、 サブクラスの ChequePayment データを取得しないように指定するためのものです。

9.1.4. table-per-subclass と table-per-class-hierarchy の混合

このアプローチを使用すると、table-per-hierarchy と table-per-subclass 戦略を 組み合わせる事も可能です。

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

いずれのマッピング戦略であっても、ルートである Payment クラスへの ポリモーフィックな関連は <many-to-one> を使ってマッピングします。

<many-to-one name="payment" column="PAYMENT_ID" class="Payment"/>

9.1.5. 具象クラスごとのテーブル(table-per-concrete-class)

table-per-concrete-class 戦略のマッピングに対するアプローチは、2つあります。 1つ目は <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>

サブクラスごとに3つのテーブルが必要です。それぞれのテーブルは、継承プロパティを含んだ、 クラスの全てのプロパティに対するカラムを定義します。

このアプローチには制限があります。 それは、プロパティがスーパークラスにマッピングされていた場合、 全てのサブクラスにおいてカラム名が同じでなければならないというものです。 (Hibernateの今後のリリースで緩和されるかもしれません)。 <union-subclass> を使った table-per-concrete-class 戦略では識別子生成戦略を使用できません。 主キーを生成するためのシードは、全ての union subclass の階層内で共有する必要があるからです。

もしスーパークラスが抽象クラスなら、abstract="true"とマッピングします。 もちろん、スーパークラスが抽象クラスでないなら、スーパークラスのインスタンスを 保持するためのテーブルの追加が必要となります(上の例でのデフォルトは PAYMENT )。

9.1.6. 暗黙的ポリモーフィズムを用いた table-per-concrete-class

もう一つのアプローチは暗黙的ポリモーフィズムの使用です。

<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がポリモーフィックなクエリの実行時にSQL UNION を生成しない点です。

このマッピング戦略に対しては、Payment へのポリモーフィックな関連は 通常、<any> を使ってマッピングされます。

<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>

9.1.7. 他の継承マッピングと暗黙的ポリモーフィズムの組み合わせ

このマッピングについての更なる注意点があります。 サブクラスが自身を <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 (とCreditCardPaymentのサブクラス、Payment の実装であるため)、 および、CashPaymentChequePayment のインスタンスを返します。 NonelectronicTransaction インスタンスは返しません。

9.2. 制限

table-per-concrete-class マッピング戦略への「暗黙的ポリモーフィズム」アプローチには いくつかの制限があります。<union-subclass> マッピングに対しても 少し弱めの制限があります。

次のテーブルに、Hibernateにおけるtable-per-concrete-classマッピングの 制限や暗黙的ポリモーフィズムの制限を示します。

表 9.1. 継承マッピングの機能

継承戦略多対一のポリモーフィズム一対一のポリモーフィズム一対多のポリモーフィズム多対多のポリモーフィズムポリモーフィズムを使ったload()/get()ポリモーフィズムを使ったクエリポリモーフィズムを使った結合外部結合によるフェッチ
table per class-hierarchy<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サポート
table per subclass<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サポート
table per concrete-class (union-subclass)<many-to-one><one-to-one><one-to-many> (for inverse="true" only)<many-to-many>s.get(Payment.class, id)from Payment pfrom Order o join o.payment pサポート
table per concrete class (implicit polymorphism)<any>サポートしていませんサポートしていません<many-to-any>s.createCriteria(Payment.class).add( Restrictions.idEq(id) ).uniqueResult()from Payment pサポートしていませんサポートしていません