ファイヤープロジェクト
1対多関連(Hibernate3)
2005-04-22T00:20+09:00   matsu
多対1を以前に調査したが,hibernateは逆に1対多もなんとかしてくれるようなので,調査してみた.
本頁のサンプルでは,以下のテーブルを扱う.
これは前回の複合キーのサンプルで作成したテーブルと同じである. すでに多対1のマッピングがなされているので,これにさらに逆の1対多のマッピングを記述する. すなわち,Outline:Detail,Detail:MoreDetailが1対多なので,OutlineからDetail,DetailからMoreDetailを引っ張れるようにする.
Detailのマッピングでは,前回までのサンプルで既にOutlineに対する多対1のマッピングがある.
<key-many-to-one name="outline" class="Outline" column="ID"/>
主キーの一部でもあったので,many-to-oneではなくkey-many-to-oneだが,考え方はmany-to-oneと同じである. 逆に1対多のマッピングを行うには,Outlineのマッピングに要素one-to-menyを使用して記述する.
1対多ということで,Outlineを参照するDetailは複数あり,よってOutlineの該当メンバは複数のDetailオブジェクトを保持する必要がある. そこで,要素setを使用して要素one-to-menyを囲んでいる.
<set name="details" lazy="false">
  <key column="ID"/>
  <one-to-many class="Detail"/>
</set>
Outlineオブジェクトは(java.util.)Set型のメンバdetailsがあり,その要素はOutlineと1対多で関連づくDetailオブジェクトである. その関連づけは,要素keyで示される. すなわち列IDでなされる(IDはDetailにおける外部キーである). 要素setの属性lazyについては後述する. 以上を踏まえると,OutlineのPOJOクラスは以下のようになる.
前回のサンプルまでと異なるのは,メンバdetailsとそのsetter,getterである.
DetailとMoreDetailも1対多である. OutlineとDetailとの違いは,関連づけの列が複数あることである. すなわちDetailを参照するMoreDetailの外部キーが複合キーである. Detailのマッピングファイルは以下となる.
やはり要素keyと要素one-to-manyを要素setで囲む.
<set name="moreDetails" lazy="false">
  <key>
    <column name="ID"/>
    <column name="REGIST_DATE"/>
  </key>
  <one-to-many class="MoreDetail"/>
</set>
DetailとMoreDetailは二つの列IDとREGIST_DATEで関連づくので,要素keyが二つの子要素columnをもち,その要素columnで複数の関連づけのキー(IDとREGIST_DATE)を指定している. 要素one-to-manyに関しては同じ. DetailのPOJOクラスはOutlineと同じ要領で,以下のようになる.
以上のマッピングを使用して,1対多参照を行うサンプルを作成した.
ポイントは二つのメソッド,
  • referDetailsRelatedToOutline(SessionFactory sessionFactory, Outline outline)
  • referMoreDetailsRelatedToDetail(SessionFactory sessionFactory, Detail detail)
である. どちらもそれぞれgetDetails,getMoreDetailsでjava.util.Set型の返り値をとる. そのSetオブジェクトにはそれぞれ該当Outlineオブジェクトに紐づく(複数の)Detailオブジェクト,該当Detailオブジェクトに紐づく(複数の)MoreDetailオブジェクトが格納されちている. 実行ログを見ると,以下のSQLが生成,発行されていることが分かる.
  1. Outlineの取得
    select outline0_.ID as ID, outline0_.CODE as CODE0_ from Outline outline0_
    
  2. 各Outlineについて,紐づくDetailの取得(複数回)
    select details0_.ID as ID__, details0_.REGIST_DATE as REGIST2___,
      details0_.ID as ID0_, details0_.REGIST_DATE as REGIST2_0_
      from Detail details0_ where details0_.ID=?
    
  1. Detailの取得
    select detail0_.ID as ID, detail0_.REGIST_DATE as REGIST2_ from Detail detail0_
    
  2. 各Detailについて,紐づくMoreDetailの取得(複数回)
    select moredetail0_.ID as ID__, moredetail0_.REGIST_DATE as REGIST2___,
      moredetail0_.SEQ_NO as SEQ3___, moredetail0_.ID as ID0_,
      moredetail0_.REGIST_DATE as REGIST2_0_, moredetail0_.SEQ_NO as SEQ3_0_,
      moredetail0_.VALUE as VALUE2_0_
      from MoreDetail moredetail0_
      where moredetail0_.ID=? and moredetail0_.REGIST_DATE=?
    
    サンプルには注意点がある. サンプルは以下の処理手順となっている.
    1. Outlineの生成
    2. Outlineの読み込み
    3. Detailの生成
    4. 読み込んでおいたOutlineから紐づくDetailを見る→0件
    5. Outlineを再度読み込み
    6. 読み込んでおいたOutlineから紐づくDetailを見る→複数件
    7. DetailとMoreDetailについても同様
    すなわち,1対多において,1側を読み込んだ時点で該当の1側オブジェクトを参照している多側オブジェクトしか参照できないということである. 1側を読み込んで,あとからその1側オブジェクトに関連づく多側オブジェクトを作成しても,getDetailsやgetMoreDetailsでは引っ張れない. 再度1側を読み込む必要がある. 実はこれは要素Setの属性lazyの値をfalseにしていることによるらしい. lazyをtrueとすると,getDetailsやgetMoreDetailsを行った時点でSQLが発行されて,最新の紐づき状況に応じたオブジェクトを参照できる...はずらしい. が,普通にやると以下の例外が発生した.
    2005-04-22 00:09:27,649 ERROR [main] hibernate.LazyInitializationException:19
     - failed to lazily initialize a collection 
    (org.fireproject.hibernatesample.Outline.details)
     - no session or session was closed
    org.hibernate.LazyInitializationException: failed to lazily initialize a collection
    (org.fireproject.hibernatesample.Outline.details)
     - no session or session was closed
      at org.hibernate.collection.AbstractPersistentCollection.initialize
    (AbstractPersistentCollection.java:179)
      at org.hibernate.collection.AbstractPersistentCollection.read
    (AbstractPersistentCollection.java:47)
      at org.hibernate.collection.PersistentSet.size
    (PersistentSet.java:109)
      at org.fireproject.hibernatesample.CompositKey.referDetailsRelatedToOutline
    (CompositKey.java:168)
      at org.fireproject.hibernatesample.CompositKey.main
    (CompositKey.java:35)
    
    本家の記述によると,lazyの値をtrueにして動作させるには,classファイルに対して何かしなければならない. サンプルでも
    $ ant
    $ ant instrument
    $ java -jar hibernatesample.jar 
    
    としたらlazyの値がtrueでも例外が発生せずに動作した. しかし,getDetailsやgetMoreDetails時に勝手にSQL発行という動きにはなっていない... うーん.
※ lazy ... 怠惰な
matsu(C)
Since 2002
Mail to matsu