Hibernateは3つの基本的な継承のマッピング戦略をサポートします。
table per class hierarchy -1つのクラス階層ごとに1つのテーブル
table per subclass -1つのサブクラスごとに1つのテーブル
table per concrete class -1つの具象クラスごとに1つのテーブル(いくつか制限があります)
同じ継承階層の異なる枝について異なるマッピング戦略を使用することは可能ですが、table-per-concrete-classマッピングを適用する場合と同じ制限が適用されます。Hibernateは同じ<class>要素内で<subclass>マッピングと<joined-subclass>マッピングを一緒に使うことはサポートしていません。
Paymentインターフェースとそれを実装するCreditCardPayment, CashPayment, ChequePaymentがあると仮定してください。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"> ... </subclass> <subclass name="CashPayment" discriminator-value="CASH"> ... </subclass> <subclass name="ChequePayment" discriminator-value="CHEQUE"> ... </subclass> </class>
このマッピングにはちょうど1つのテーブルが要求されます。このマッピング戦略には1つ大きな制限があります。それはサブクラスによって宣言されたカラムはNOT NULL制約をもてないことです。
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"/> ... </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つのサブクラスのテーブルは、スーパークラスのテーブルへの主キー関連を持っています(したがって、リレーショナル・モデルは現実にone-to-one関連です)。
Hibernateのtable-per-subclass戦略の実装はdiscriminatorカラムを要求されないことに注意してください。他のO/RマッパーはHibernateとは違って、サブクラスのテーブルにdiscriminatorカラムを要求するtable-per-subclass戦略の実装を用いています。Hibernateが取ったアプローチは実装することは難しくなりますが関連の視点からみて恐らくより正確です。
これら2つのどちらのマッピング戦略にとっても、Paymentへのポリモーフィックな関連は<many-to-one>を使ってマッピングします。
<many-to-one name="payment" column="PAYMENT" class="Payment"/>
table-per-concrete-classマッピングは他の2つとは非常に異なります。
<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>
このマッピングには3つのテーブルが要求されます。Paymentインターフェースを明示的に言及する場所がないことに注意してください。代わりにHibernateの暗黙のポリモーフィズムを利用します。まPaymentのプロパティがそれぞれのサブクラスにマッピングされることにも注 意してください。
この場合、ポリモーフィズムを使ったPaymentへの関連は<any>を使ってマッピングされます。
<any name="payment" meta-type="class" id-type="long"> <column name="PAYMENT_CLASS"/> <column name="PAYMENT_ID"/> </any>
discriminator型の文字列からPaymentのサブクラスへのマッピングを扱うために、meta-typeとしてUserTypeを定義することはよりいいことでしょう。
<any name="payment" meta-type="PaymentMetaType" id-type="long"> <column name="PAYMENT_TYPE"/> <!-- CREDIT, CASH 又は CHEQUE --> <column name="PAYMENT_ID"/> </any>
このマッピングに関して気づくことがもう1つあります。サブクラスはそれぞれを<class>要素としてマッピングしているので(かつPaymentは単なるインターフェースなので)、それぞれのサブクラスは簡単にtable-per-classマッピングやtable-per-subclassマッピングの継承階層の一部となれます!(また、今までどおり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は自動的にNonel ectronicTransactionのインスタンスではなくCreditCardPayment(そしてそのサブクラスを返します。なぜならそれらはPaymentの実装だからです)、CashPayment および ChequePayment のインスタンスを返します。
Hibernateでは関連はちょうど1つの外部キーカラムとマッピングすると想定します。1つの外部キーによる多重関連は許容されますが(inverse="true"や、insert="false" update="false"と指定する必要がある)、しかし多重外部キーへの任意の関連をマッピングする方法はありません。このことは次のことを意味します。:
関連が修正されるとき、更新されるのは常に同じ外部キーです。
関連がlazyに取ってこられる場合、単一のデータベース・クエリが使用されます。
関連がeagerに取ってこれらる場合、1つのアウター・ジョインを使ってフェッチされます。
それは特にtable-per-concrete-class戦略を使ってマッピングされたクラスへのポリモーフィズムを使ったone-to-many関連はサポートされていないということを暗示しています(この関連を取って来ることは多数のクエリ、あるいは多数のジョインを要求するでしょう)。
次のテーブルはHibernateにおけるtable-per-concrete-classマッピングの制限と、暗黙ポリモーフィズムの制限を示しています。
表 8.1. 継承のマッピングの特徴
継承戦略 | many-to-oneのポリモーフィズム | one-to-oneのポリモーフィズム | one-to-manyのポリモーフィズム | many-to-manyのポリモーフィズム | ポリモーフィズムを使った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 p | from Order o join o.payment p | supported |
table-per-subclass | <many-to-one> | <one-to-one> | <one-to-many> | <many-to-many> | s.get(Payment.class, id) | from Payment p | from Order o join o.payment p | supported |
table-per-concrete-class (implicit polymorphism) | <any> | サポートしていません | サポートしていません | <many-to-any> | クエリの使用 | from Payment p | サポートしていません | サポートしていません |