ファイヤープロジェクト
継承 その3(Hibernate3)
2005-04-24T21:30+09:00   matsu
二つのテーブルをそれぞれサブクラスにマッピングさせるパターン(親クラスに対応するテーブルがない)を調査してみた.
本頁のサンプルでは,以下のテーブルを扱う.
下図のように,テーブルには子クラスにあたる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とする必要がある.
any
クラスBesideのマッピングでは,対応するテーブルがない親クラスSuperClassをどう記述するかがポイントとなる. 要件は三つ.
  1. Besideのレコード毎に,含有するSuperClassのインスタンスがSub1かSub2に分かれる.
  2. 含有するのがSub1かSub2かは該当Besideレコードの列TYPEの値で判定される.
  3. BesideレコードとSuperClass(のサブクラス)のレコードは,SUPER_CLASS_ID,IDで関連づけられる. (テーブル定義にはないが,BesideテーブルのSUPER_CLASS_IDはSuperClassを参照する外部キーである.)
Besideのマッピングは以下となる.
ポイントは要素anyである. クラスBesideのメンバであるsuperClassは,対応するテーブルがない. そこで,以下のようにテーブルBesideの列TYPEの値に応じてテーブルがSub1,Sub2のどちらなのかを特定し,テーブルBesideの列SUPER_CLASS_IDの値で特定したSub1あるいはSub2のレコードを特定するように記述する.
<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を記述する. 従来の考え方でいう,外部キーの指定である.
Besideレコードを挿入時,hibernateは,TYPEの値として,デフォルトではsuperClassのクラスのFQCNを設定する. すなわちsuperClassの値がSub1なら列TYPEの値は"org.fireproject.hibernatesample.Sub1"といった具合である. これは無駄なスペースをとるし,なにより列TYPEはCHAR(4)である(DBが怒る). そこで,要素meta-valueで,TYPEへの設定値を"SUB1","SUB2"と置き換えるように指定している. サンプルを実行すると,Besideをロード時,以下のSQLが発行される.
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制約やアプリケーションで保証する必要がある.
matsu(C)
Since 2002
Mail to matsu