Red Hat Training

A Red Hat training course is available for Red Hat JBoss Web Server

第10章 継承マッピング

10.1. 3つの戦略 

Hibernate は3つの基本的な継承のマッピング戦略をサポートします。
  • クラス階層ごとのテーブル (table-per-class-hierarchy)
  • サブクラスごとのテーブル(table-per-subclass)
  • 具象クラスごとのテーブル (table-per-concrete-class)
加えて4つ目に、 Hibernate はわずかに異なる性質を持ったポリモーフィズムをサポートします。
  • 暗黙的ポリモーフィズム
同じ継承階層にある別の分岐に対して、異なるマッピング戦略を利用することができます。そうすることで、暗黙的ポリモーフィズムを使い、階層全体でポリモーフィズムを実現することができます。しかし、Hibernate は<subclass><joined-subclass><union-subclass> マッピングを同じroot の<class> 要素の下に混在させることができません。<subclass><join> 要素を組み合わせることで、クラス階層毎のテーブルとサブクラス戦略毎のテーブルを同じ<class> 要素の下で混在させることは可能です (以下の例を参照)。
subclassunion-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>

10.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>
ちょうど一つのテーブルが必要です。このマッピング戦略には制限が1つあります。CCTYPE のような、サブクラスで宣言されたカラムは NOT NULL 制約を持つことができません。

10.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つのサブクラステーブルはスーパークラステーブルとの関連を示す主キーを持っており、実際、関係モデル上は一対一関連です。

10.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 データを取得しないように指定するためのものです。

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

クラス階層毎のテーブルとサブクラス戦略毎のテーブルを以下のアプローチを使うことで混在させることもできます。
<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"/>

10.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つのテーブルが必要です。それぞれのテーブルは、継承プロパティを含んだ、クラスの全てのプロパティに対するカラムを定義します。
このアプローチにおける制限は、プロパティがスーパークラスにマッピングされていた場合、全てのサブクラスにおいてカラム名が同じでなければならないというものです。union subclass 継承では識別子生成戦略を使用できません。主キーを生成するためのシードは、全ての union subclass の階層内で共有する必要があるからです。
スーパークラスが抽象的であれば、abstract="true" とマッピングします。もちろん、スーパークラスが抽象的でないなら、スーパークラスのインスタンスを保持するために、テーブルの追加が必要となります (上の例でのデフォルトは PAYMENT )。

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

10.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 (と Payment の実装であるためCreditCardPayment のサブクラス)、および、CashPaymentChequePayment のインスタンスを返します。しかし、NonelectronicTransaction インスタンスは返しません。