Hibernate 入門記 番外編 外部結合まとめ
某巨大掲示板に この日記の URL が貼られてました.かなりドキドキ.
また写真が増えておるよ.このサイト.
そんなわけで (どんなわけで?),Hibernate の問い合わせと外部結合について軽くまとめてみるテスト.
話題になっているのは多対 1 の問い合わせ.この日記的にはモデルと雑誌みたいな.
ということで,マッピングファイルだけ掲載.
まずはモデル.
<?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" default-access="field"> <class name="Model" lazy="false"> <id name="id" unsaved-value="-1"> <generator class="identity"/> </id> <property name="name"/> <many-to-one name="magazine" class="Magazine"/> </class> </hibernate-mapping>
そして雑誌.
<?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" default-access="field"> <class name="Magazine"> <id name="id" unsaved-value="-1"> <generator class="identity"/> </id> <property name="name"/> </class> </hibernate-mapping>
これを次のように問い合わせします.
Iterator it = session.find("from study.Model model where model.name='Yuri Ebihara'").iterator();
すると...
Hibernate: select model0_.id as id, model0_.name as name, model0_.magazine as magazine from Model model0_ where (model0_.name='Yuri Ebihara' ) Hibernate: select magazine0_.id as id0_, magazine0_.name as name0_ from Magazine magazine0_ where magazine0_.id=? Yuri Ebihara (CanCam)
まずはモデルだけが問い合わせされて,その後雑誌への問い合わせが行われます.
outer-join="true"
を指定しても,勝手に外部結合はしてくれませんから!! 残念!!!!
じゃあ outer-join="true"
はなんなのかというと,find()
ではなく,load()/get()
を使った場合に効果があります.
Model model = (Model) session.get(Model.class, new Integer(1));
この場合だと,
Hibernate: select model0_.id as id1_, model0_.name as name1_, model0_.magazine as magazine1_, magazine1_.id as id0_, magazine1_.name as name0_ from Model model0_ left outer join Magazine magazine1_ on model0_.magazine=magazine1_.id where model0_.id=? Yuri Ebihara (CanCam)
外部結合してくれました.
outer-join="false"
を指定すると外部結合しなくなります.
でもでも,load()/get()
を使えるのは ID プロパティ (主キー) による問い合わせのみ.最初の例のように,任意のプロパティを指定した問い合わせには使えませんから!! 残念!!!!
そこで,明示的な外部結合が必要になります.
Iterator it = session.find( "from study.Model model left outer join model.magazine " + "where model.name = 'Yuri Ebihara'").iterator();
その結果.
Hibernate: select model0_.id as id0_, magazine1_.id as id1_, model0_.name as name0_, model0_.magazine as magazine0_, magazine1_.name as name1_ from Model model0_ left outer join Magazine magazine1_ on model0_.magazine=magazine1_.id where (model0_.name='Yuri Ebihara' ) [Yuri Ebihara (CanCam), CanCam]
結合してくれるのはいいのですが,結果が配列で返って来ちゃうのが難点.
そこで,FETCH JOIN を使ってみましょう.
Iterator it = session.find( "from study.Model model left outer join fetch model.magazine " + "where model.name = 'Yuri Ebihara'").iterator();
その結果.
Hibernate: select model0_.id as id0_, magazine1_.id as id1_, model0_.name as name0_, model0_.magazine as magazine0_, magazine1_.name as name1_ from Model model0_ left outer join Magazine magazine1_ on model0_.magazine=magazine1_.id where (model0_.name='Yuri Ebihara' ) Yuri Ebihara (CanCam)
バッチリ!! でしょ?>掲示板の人
今回,FETCH JOIN で話がすんだのは多対1だからです.
1対多だと,多の側の数だけ1の方が返って来ちゃいます.
ということで,さっきはモデルから雑誌への1対多だった関連を,今度は雑誌からモデルへの1対多に変更します.
マッピングファイルのみ掲載.まずは雑誌.
<?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" default-access="field"> <class name="Magazine"> <id name="id" unsaved-value="-1"> <generator class="identity"/> </id> <property name="name"/> <set name="model"> <key column="magazine"/> <one-to-many class="Model"/> </set> </class> </hibernate-mapping>
そしてモデル.
<?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" default-access="field"> <class name="Model" lazy="false"> <id name="id" unsaved-value="-1"> <generator class="identity"/> </id> <property name="name"/> </class> </hibernate-mapping>
でもって,次の問い合わせ.
Iterator it = session.find( "from study.Magazine magazine left outer join fetch magazine.model model " + "where magazine.name = 'CanCam'").iterator();
その結果.
Hibernate: select magazine0_.id as id0_, model1_.id as id1_, magazine0_.name as name0_, model1_.name as name1_, model1_.magazine as magazine__, model1_.id as id__ from Magazine magazine0_ left outer join Model model1_ on magazine0_.id=model1_.magazine where (magazine0_.name='CanCam' ) CanCam [Yuri Ebihara, Asami Usuda] CanCam [Yuri Ebihara, Asami Usuda]
ということで,関連するモデルの数 (2件) だけ雑誌が返って来ちゃうのです.残念!!!!
これに対処する方法のひとつはクリテリアを使うこと.
Iterator it = session .createCriteria(Magazine.class) .add(Expression.eq("name", "CanCam")) .setFetchMode("model", FetchMode.EAGER) .setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY) .list().iterator();
その結果.
Hibernate: select this.id as id1_, this.name as name1_, model1_.magazine as magazine__, model1_.id as id__, model1_.id as id0_, model1_.name as name0_ from Magazine this left outer join Model model1_ on this.id=model1_.magazine where this.name=? CanCam [Asami Usuda, Yuri Ebihara]
バッチリ!!
でもでも,クリテリアを使うと問い合わせの外部化が出来ないんですよね.可読性もイマイチだし.
そこで,Iterator
のラッパーを作ってみました.
package study; import java.util.Iterator; import java.util.NoSuchElementException; public class DistinctIterator implements Iterator { private Iterator iterator; private Object prev; private Object next; public DistinctIterator(Iterator iterator) { this.iterator = iterator; } public boolean hasNext() { if (prev == null) { return iterator.hasNext(); } if (next != null) { return true; } while (iterator.hasNext()) { next = iterator.next(); if (next != prev) { return true; } } next = null; return false; } public Object next() { if (prev == null) { prev = iterator.next(); return prev; } if (next != null || hasNext()) { prev = next; next = null; return prev; } throw new NoSuchElementException(); } public void remove() { throw new UnsupportedOperationException(); } }
テストファーストはもちろん,単体テストさえしていないので,バグバグの可能性もあると思いますが... 心より恥じる.
こいつを使った問い合わせ.
Iterator it = new DistinctIterator(session.find( "from study.Magazine magazine left outer join fetch magazine.model model " + "where magazine.name = 'CanCam'") .iterator());
そして実行!!!!
Hibernate: select magazine0_.id as id0_, model1_.id as id1_, magazine0_.name as name0_, model1_.name as name1_, model1_.magazine as magazine__, model1_.id as id__ from Magazine magazine0_ left outer join Model model1_ on magazine0_.id=model1_.magazine where (magazine0_.name='CanCam' ) CanCam [Asami Usuda, Yuri Ebihara]
バッチリ!!