Hibernate 入門記 継承その2 table per subclass

継承の2回目です.
今回は,サブクラスごとに個別のテーブルにマッピングする方法について学習します.
サブクラスごとというか,継承階層のクラスごとといった方が適切かも.ルートクラスもテーブルにマップされるので.
ちなみに EJB 3.0(Early Draft) では,Joined Subclass Strategy に相当します.


このマッピングでは,継承階層のルートクラスがマッピングされるテーブルには,ルートクラスの永続プロパティに対応するカラムだけを持ちます.
そして,サブクラスごとに,そのサブクラス固有の永続プロパティを保持するカラムを持ったテーブルを用意します.
これらのテーブルを結合することで,永続オブジェクトの全てのプロパティが揃います.
テーブルを結合するにはキーが必要です.サブクラスがマッピングされるテーブルには,ルートクラスを参照する外部キーが必要となります.このマッピング方法の制限はこれくらいみたい.
前回の table per class hierarchy とは異なり,discriminator カラムは不要です.びっくり.これは,Hibernate の実装にとっては大変だとか.そーなんだぁ.とりあえず感謝しておこう.


テーブルの持ち方がわかったところで,マッピングについて学習します.
前回の table per class hierarchy と同様に,クラス階層全体を一つの

  • <class> 要素

で記述します.
ID プロパティの定義,プロパティおよび関連の定義の後に,各サブクラスのマッピングについて

  • <joined-subclass> 要素

で記述します.
5.1.14. joined-subclass」によると,こいつは次の属性を持っています.

name
クラス名を指定します.必須です.
proxy
遅延初期化する場合に指定するようですが,関連以外の遅延初期化はまだ学習していません.無念だ.
lazy
同上.
dynamic-update
すでにお馴染み.永続オブジェクトを更新した際に,変更のあったプロパティ (カラム) だけを更新するような SQL を発行する場合に true を指定します.デフォルトは false です.
dynamic-insert
上記の INSERT 版.

前回の <subclass> 要素とほぼ同じ.discriminator-value 属性がないだけです.よって,上のはコピペです.心より恥じる.
そして <joined-subclass> 要素の内容には,まず

  • <key> 要素

を記述します.
これは,ルートクラスがマッピングされるテーブルへの外部キーを指定するものです.属性などはこれまでに学習済み.
その後に,プロパティや関連のマッピングを記述します.
それに続けて,<joined-subclass> 要素も記述することが出来ます.深い深ーいクラス階層も記述できるのですね (こぴぺ).


さぁ,お試し開始です.
ネタは前回と同じで行きましょう.
まずはテーブル定義.

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

CREATE TABLE MODEL (
    ID INTEGER PRIMARY KEY,
    MAGAZINE VARCHAR
)

CREATE TABLE ACTRESS (
    ID INTEGER PRIMARY KEY,
    DRAMA VARCHAR
)

ルートクラスとサブクラスが二つで,合計三つのテーブルを用意しなくてはならないのですね.結構大変.
永続クラスについては,TalentModelActress とも前回と同じです.たぶん.
そしてタレントをルートとするクラス階層のマッピングファイル,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"/>

        <joined-subclass name="Model">
            <key column="id"/>
            <property name="magazine" access="field"/>
        </joined-subclass>
 
        <joined-subclass name="Actress">
            <key column="id"/>
            <property name="drama" access="field"/>
        </joined-subclass>
    </class>
</hibernate-mapping>

そして実行用のクラスも前回と同じです.
それを実行!!!!

 onSave() : Yuri Ebihara
 Hibernate: insert into Talent (name, id) values (?, null)
 Hibernate: CALL IDENTITY()
 Hibernate: insert into Model (magazine, id) values (?, ?)
 onSave() : Akiko Yada
 Hibernate: insert into Talent (name, id) values (?, null)
 Hibernate: CALL IDENTITY()
 Hibernate: insert into Actress (drama, id) values (?, ?)
 onSave() : Kyoko Uchida
 Hibernate: insert into Talent (name, id) values (?, null)
 Hibernate: CALL IDENTITY()
 Hibernate: select talent0_.id as id, 
                 casewhen(talent0__1_.id is not null, 1,  
                     casewhen(talent0__2_.id is not null, 2,  
                         casewhen(talent0_.id is not null, 0, -1))) as clazz_, 
                 talent0_.name as name0_, talent0__1_.magazine as magazine1_, 
                 talent0__2_.drama as drama2_ 
             from Talent talent0_ 
                 left outer join Model talent0__1_ on talent0_.id=talent0__1_.id 
                 left outer join Actress talent0__2_ on talent0_.id=talent0__2_.id
 onLoad() : Yuri Ebihara
 onLoad() : Akiko Yada
 onLoad() : Kyoko Uchida
 Yuri Ebihara : CanCam
 Akiko Yada : My Little Chef
 Kyoko Uchida

またしても楽勝.いいねぇー (昔のアコム風)
にしても,この SELECT は... casewhen入れ子になった clazz_ の値で結果セットの行がどのクラスのインスタンスになるかを示す,discriminator カラムの役割を果たしているようですね.たしかに大変そう.よく頑張った!
でも,深い深ーい継承階層の場合の SQL を考えると... 怖ひぃ〜.


次回は残る一つのマッピング方式,table per concrete class へ進みます.