継承 その3(Hibernate3)
二つのテーブルをそれぞれサブクラスにマッピングさせるパターン(親クラスに対応するテーブルがない)を調査してみた.
本頁のサンプルでは,以下のテーブルを扱う.
下図のように,テーブルには子クラスにあたるSub1,Sub2そしてそれらを参照するBesideがある.
親クラスにあたるテーブルSuperClassはなく(※),前頁までテーブルSuperClassにあった列はSub1,Sub2がそれぞれで持っている.
テーブルBesideはSub1あるいはSub2への参照を列SUPER_CLASS_IDで保持するが,DBMSは参照整合性をチェックしない.
テーブルBesideのレコードがそれぞれSub1,Sub2どちらへの参照をもつかは,該当レコードの列TYPEにて判定する.
この列TYPEは前頁のサンプルでのDESCRIMINATORと似ている.
クラス図は以下となる.
対応するテーブルはないが,やはり親クラスSuperClassがあり,BesideはSuperClassを含有する.
以上のように,個人的には割りとアクロバットなマッピングのように感じるが,こういうこともできるということで,意向にその方法を記述していく.
※ 図では概念的な対象として灰色で記述した.
サンプルでのサブクラスSub1,Sub2のマッピングは以下である.
シンプルなマッピングとほぼ同じである.
親クラスに対応するテーブルがない以上,あまり凝った要求,記述はできない(あるいはしようがない)ということだろう.
アプリケーション側で期待したいこととすれば,親クラスに対してロードをかけたときに,サブクラスを芋ずる式にロードすることだろうか.
要素classの属性polymorphismの値がそれを指定する.
<class name="Sub1" table="Sub1" polymorphism="implicit"> ... <class name="Sub2" table="Sub2" polymorphism="implicit">これで,例えば
session.createQuery("from org.fireproject.hibernatesample.SuperClass");
とすると,SuperClassの子クラスでかつ属性polymorphismの値がimplicitとなっているテーブルは全てロードされる(※).
実際,サンプルを実行すると,以下のような二つのSQLが発行される.
select sub2x0_.ID as ID, sub2x0_.SUPERCLASS_VALUE as SUPERCLASS2_1_, sub2x0_.SUB2_VALUE as SUB3_1_ from Sub2 sub2x0_ select sub1x0_.ID as ID, sub1x0_.SUPERCLASS_VALUE as SUPERCLASS2_0_, sub1x0_.SUB1_VALUE as SUB3_0_ from Sub1 sub1x0_また,Sub1とSub2両方でidが一意となるように,idの採番にはsequenceを使用した.
<generator class="sequence"> <param name="sequence">id_sequence</param> </generator>
※ 親クラスのマッピング記述がないので,親クラスをHQLで指定する場合はFQCNとする必要がある.
クラスBesideのマッピングでは,対応するテーブルがない親クラスSuperClassをどう記述するかがポイントとなる.
要件は三つ.
ポイントは要素anyである.
クラスBesideのメンバであるsuperClassは,対応するテーブルがない.
そこで,以下のようにテーブルBesideの列TYPEの値に応じてテーブルがSub1,Sub2のどちらなのかを特定し,テーブルBesideの列SUPER_CLASS_IDの値で特定したSub1あるいはSub2のレコードを特定するように記述する.
- Besideのレコード毎に,含有するSuperClassのインスタンスがSub1かSub2に分かれる.
- 含有するのがSub1かSub2かは該当Besideレコードの列TYPEの値で判定される.
- BesideレコードとSuperClass(のサブクラス)のレコードは,SUPER_CLASS_ID,IDで関連づけられる. (テーブル定義にはないが,BesideテーブルのSUPER_CLASS_IDはSuperClassを参照する外部キーである.)
<any name="superClass"
meta-type="string"
id-type="java.lang.Integer">
<meta-value value="SUB1" class="Sub1"/>
<meta-value value="SUB2" class="Sub2"/>
<column name="TYPE"/>
<column name="SUPER_CLASS_ID"/>
</any>
要素anyの属性meta-typeはタイプ指定する列(ここでは列TYPE)の型である.
要素anyの属性id-typeは参照しているテーブルレコードを特定する際のID(ここではSUPER_CLASS_ID)の型である.
要素anyの子要素にはmeta,meta-valueが0個以上続いたあと,columnが二つ以上続く.
<!ELEMENT any (meta*,meta-value*,column,column+)>要素anyの子要素columnの設定内容は以下である.
- 一つ目の要素columnには,タイプを特定する列を記述する. 前頁のサンプルでいうDESCRIMINATORの列である.
- 二つ目以降の要素columnには,特定したタイプの行を特定するIDを記述する. 従来の考え方でいう,外部キーの指定である.
select beside0_.ID as ID, beside0_.BESIDE_VALUE as BESIDE2_2_, beside0_.TYPE as TYPE2_, beside0_.SUPER_CLASS_ID as SUPER4_2_ from Beside beside0 select sub1x0_.ID as ID0_, sub1x0_.SUPERCLASS_VALUE as SUPERCLASS2_0_0_, sub1x0_.SUB1_VALUE as SUB3_0_0_ from Sub1 sub1x0_ where sub1x0_.ID=? select sub1x0_.ID as ID0_, sub1x0_.SUPERCLASS_VALUE as SUPERCLASS2_0_0_, sub1x0_.SUB1_VALUE as SUB3_0_0_ from Sub1 sub1x0_ where sub1x0_.ID=? select sub2x0_.ID as ID0_, sub2x0_.SUPERCLASS_VALUE as SUPERCLASS2_1_0_, sub2x0_.SUB2_VALUE as SUB3_1_0_ from Sub2 sub2x0_ where sub2x0_.ID=? select sub2x0_.ID as ID0_, sub2x0_.SUPERCLASS_VALUE as SUPERCLASS2_1_0_, sub2x0_.SUB2_VALUE as SUB3_1_0_ from Sub2 sub2x0_ where sub2x0_.ID=?まずBesideレコードを読み込み,各レコード毎にFrom句のテーブルをTYPEの値に応じたSub1,Sub2を切替えつつ,ID値を指定したSelectを発行している. 以上見てくると,この頁の方法は,DBが参照整合性を保証できないことがわかる. そのあたりはCheck制約やアプリケーションで保証する必要がある.

