Hibernate 入門記 継承その3 table per concrete class

継承の3回目です.
今回は,コンクリートクラスすなわちインスタンスの実際のクラスごとに,個別のテーブルにマッピングする方法について学習します.
EJB 3.0 (Early Draft) では,「Single Table per Class Strategy」に相当します.
このマッピングは,これまでに学習した二つのマッピングに比べてずいぶんと制限が多いようで,関連の扱いなどが変わってしまうみたいです.
そこで,今回はまず継承のマッピング自体を学習し,次回に関連のマッピングについて学習することにしたいと思います.


このマッピングでは,インスタンスを生成することができる (抽象でない) クラスごとに一つのテーブルを用意します.それぞれのテーブルは,スーパークラスで定義されたものも含めて,全ての永続プロパティに対応するカラムを持たなくてはなりません.なんて大変なんだ!


そしてマッピングですが,これまで学習した二つの継承マッピングとは大きく異なり,各クラスごとに個別の

  • <class> 要素

を記述します.ここでいう各クラスとは,継承階層の中で,インスタンスを生成できるクラスのことです.
各クラスの <class> 要素では,スーパークラスで定義されたものも含めて,全てのプロパティ (ID プロパティを含む) や関連についてのマッピング定義を記述します.
これだと,それぞれのクラスは全く関係のないクラスとして扱われてしまうような気がしてしまったのですが,そうではないようです.
実は,Hibernate は永続クラスの継承階層を解析してくれるので,個別の <class> 要素で定義されたクラスであっても,「ポリモーフィックな問い合せ」ができるみたいです.
ポリモーフィックな問い合わせ」というのは,スーパークラスを FROM 句に指定した問い合わせで,スーパークラスインスタンスだけでなく,サブクラスのインスタンスも取得してくれるというものです.
Hibernate は,永続クラスの継承階層を解析することで,問い合わせで指定されたクラスのサブクラスを取得するように問い合わせを行ってくれるのだとか.
これを「暗黙的なポリモーフィズム」と呼ぶようです.そんなことをやってくれていたのですね.気づきませんでした.
ということは,マッピングファイルで記述したクラスでなくても問い合せで指定できるということですか.おもしろそう.
「暗黙的なポリモーフィズム」を使うかどうかは,以前学習をスキップした <class> 要素の属性で指定します.

polymorphism
ポリモーフィズムを暗黙的にする (implicit) か,明示的にする (explicit) かを指定します.
デフォルトは implicit です.

なお,「暗黙的なポリモーフィズム」が適用されるのは HQL を使った問い合せ (Session#find(String) など) だけで,Session#load(Class, Serializable)Session#get(Class, Serializable) などには適用されないとのことです.これらの場合は,スーパークラスではなく,キーで示されるインスタンスの実際のクラスを第一引数で指定する必要があるようです.これが table per concrete class の大きな制限の一つみたい.今のところ get/load は全然使っていないので問題ありませんが.
なお,これまでに学習した二つの継承マッピングでは,「明示的なポリモーフィズム」にした場合でも,スーパークラスを指定した問い合せでサブクラスのインスタンスも取得することができるようです.これは,一つの <class> 要素で継承階層を「明示的」にマッピングしているからです.


さて,とりあえず予習完了.お試しします.
またしても全く同じ例題でいきます.
まずはテーブル定義.

CREATE TABLE TALENT (
    ID INTEGER IDENTITY PRIMARY KEY,
    NAME VARCHAR
)

CREATE TABLE MODEL (
    ID INTEGER IDENTITY PRIMARY KEY,
    NAME VARCHAR,
    MAGAZINE VARCHAR
)
CREATE TABLE ACTRESS (
    ID INTEGER IDENTITY PRIMARY KEY,
    NAME VARCHAR,
    DRAMA VARCHAR
)

今回のスーパークラス (タレント) はインスタンスを生成するので,3つのテーブルを定義します.
どのテーブルも,スーパークラスのプロパティである NAME を持っています.
3つの永続クラスは,前々回と同じです.
そしてマッピングファイルですが,3つのクラスごとに用意しました.
まずはタレントのマッピングファイル,study/Talent.hbm.xml です.

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
    "-//Hibernate/Hibernate Mapping DTD 2.0//EN"
    "http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd"
>
<hibernate-mapping auto-import="false" package="study">
    <class name="Talent">
        <id name="id" access="field" unsaved-value="-1">
            <generator class="identity"/>
        </id>
        <property name="name" access="field"/>
    </class>
</hibernate-mapping>

次にモデルのマッピングファイル,study/Model.hbm.xml です.

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
    "-//Hibernate/Hibernate Mapping DTD 2.0//EN"
    "http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd"
>
<hibernate-mapping auto-import="false" package="study">
    <class name="Model">
        <id name="id" access="field" unsaved-value="-1">
            <generator class="identity"/>
        </id>
        <property name="name" access="field"/>
        <property name="magazine" access="field"/>
    </class>
</hibernate-mapping>

そして女優のマッピングファイル,study/Actress.hbm.xml です.

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
    "-//Hibernate/Hibernate Mapping DTD 2.0//EN"
    "http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd"
>
<hibernate-mapping auto-import="false" package="study">
    <class name="Actress">
        <id name="id" access="field" unsaved-value="-1">
            <generator class="identity"/>
        </id>
        <property name="name" access="field"/>
        <property name="drama" access="field"/>
    </class>
</hibernate-mapping>

この3つのマッピングファイルを,hibernate.cfg.xml に記述します.
そして実行クラスは前々回と同じ.
それを実行!!!!

 onSave() : Yuri Ebihara
 Hibernate: insert into Model (name, magazine, id) values (?, ?, null)
 Hibernate: CALL IDENTITY()
 onSave() : Akiko Yada
 Hibernate: insert into Actress (name, drama, id) values (?, ?, null)
 Hibernate: CALL IDENTITY()
 onSave() : Kyoko Uchida
 Hibernate: insert into Talent (name, id) values (?, null)
 Hibernate: CALL IDENTITY()
 Hibernate: select actress0_.id as id, actress0_.name as name, actress0_.drama as drama from Actress actress0_
 onLoad() : Akiko Yada
 Hibernate: select talent0_.id as id, talent0_.name as name from Talent talent0_
 onLoad() : Kyoko Uchida
 Hibernate: select model0_.id as id, model0_.name as name, model0_.magazine as magazine from Model model0_
 onLoad() : Yuri Ebihara
 Yuri Ebihara : CanCam
 Kyoko Uchida
 Akiko Yada : My Little Chef

おっけー.
一度の問い合せに対して,SQL が 3 回実行されていますね.これが「暗黙的なポリモーフィズム」のパワー!!
にしても,インスタンスがロードされた順番と問い合せ結果の List の順番が逆なのはなぜ? (^^;


ここで,お遊びで実行クラスの問い合せ部分を次のように変更しました.

            Iterator it = session.find("from java.lang.Object").iterator();

これでもちゃんと問い合せできました.これが「暗黙的なポリモーフィズム」のパワー!!


次に,女優のマッピングファイルを次のように修正しました (問い合せは戻しました) .

    <class name="Actress" polymorphism="explicit">

女優のみ,「明示的なポリモーフィズム」にしました.
そしてもう一度実行!!!! (SELECT 以降を抜粋)

 Hibernate: select talent0_.id as id, talent0_.name as name from Talent talent0_
 onLoad() : Kyoko Uchida
 Hibernate: select model0_.id as id, model0_.name as name, model0_.magazine as magazine from Model model0_
 onLoad() : Yuri Ebihara
 Yuri Ebihara : CanCam
 Kyoko Uchida

アッコちゃんが消えてしまいました.(;_;)
女優クラスを「明示的なポリモーフィズム」にしたために,タレントの検索では女優を探さなくなってしまったことが分かります.


今回のような継承のマッピングでは,「明示的なポリモーフィズムを使うことはあまりなさそうです.
ポリモーフィズムを明示的にするケースというのは,二つの (おそらく継承関係にない) クラスを一つのテーブルにマッピングする場合だそうです.モデリング的には変ですが,ちょっとしたクラスなんかだとありなのかも.自分が使うとは思えませんけどね♪


次回は,table per concrete class でマッピングした永続クラスへの関連について学習します.