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