Hibernate Criteriaクエリ setFetchMode でパフォーマンス改善 | fetch 戦略

2008/09/08 15:10Update
TAGS: Hibernate | fetch戦略 | Criteriaクエリ | setFetchMode

「Hibernate マッピング - fetch 戦略でパフォーマンス改善 | 概要」では、Hibernateのfetch 戦略が何の問題を解決するか、fetch 戦略とは何か、fetch の設定の方法、そしてサンプルからfetch戦略について学びました。本文は、HibernateのCriteriaでのfetch戦略( Criteria.setFetchMode )を学びます。

―――――――――――――――――――――――――――――――――
Hibernate マッピング - fetch 戦略でパフォーマンス改善 | 概要
―――――――――――――――――――――――――――――――――

問題


次のような1対多関係のテーブルがあるとします。
◇FATHERテーブル
ID    NAME    
1    f01    
2    f02    
3    f03    
4    f04    


◇SONテーブル
id    NAME    FATHER_ID    
1    s01    1    
2    s02    1    
3    s03    2    
4    s04    2    
5    s05    3    
6    s06    3    
7    s07    4    
8    s07    4    


◇Father.hbm.xml(抜粋)
        <set name="sonSet" cascade="all" inverse="true">
             <key column="FATHER_ID"/>
             <one-to-many class="test.array.Son"/>
        </set>


◇テストコード(抜粋)
    public static void testFetchCriteria(Session sess) {
        Criteria criteria = sess.createCriteria(Father.class);
        
        criteria.add(Restrictions.like("name", "f%"));
        
        List <Father> fatherList = criteria.list();
        
        for (Father father : fatherList) {
            System.out.println("==========");
            System.out.println("father: id=" + father.getId() + " name=" + father.getName());
            
            Set sonSet = father.getSonSet();
            if (sonSet != null) {
                Iterator ite = sonSet.iterator();
                
                while (ite.hasNext()) {
                    Son son =  (Son)ite.next();
                    System.out.println("son=" + son.getName());
                }
            }
        }
    }


上のコードを実行すると、次のログが出力されます。
◇出力ログ
Hibernate: select this_.ID as ID0_, this_.NAME as NAME0_0_ from FATHER this_ where this_.NAME like ?
==========
father: id=1 name=f01
Hibernate: select sonset0_.FATHER_ID as FATHER3_1_, sonset0_.id as id1_, sonset0_.id as id0_, sonset0_.NAME as NAME1_0_, sonset0_.FATHER_ID as FATHER3_1_0_ from SON sonset0_ where sonset0_.FATHER_ID=?
son=s02
son=s01
==========
father: id=2 name=f02
Hibernate: select sonset0_.FATHER_ID as FATHER3_1_, sonset0_.id as id1_, sonset0_.id as id0_, sonset0_.NAME as NAME1_0_, sonset0_.FATHER_ID as FATHER3_1_0_ from SON sonset0_ where sonset0_.FATHER_ID=?
son=s03
son=s04
==========
father: id=3 name=f03
Hibernate: select sonset0_.FATHER_ID as FATHER3_1_, sonset0_.id as id1_, sonset0_.id as id0_, sonset0_.NAME as NAME1_0_, sonset0_.FATHER_ID as FATHER3_1_0_ from SON sonset0_ where sonset0_.FATHER_ID=?
son=s06
son=s05
==========
father: id=4 name=f04
Hibernate: select sonset0_.FATHER_ID as FATHER3_1_, sonset0_.id as id1_, sonset0_.id as id0_, sonset0_.NAME as NAME1_0_, sonset0_.FATHER_ID as FATHER3_1_0_ from SON sonset0_ where sonset0_.FATHER_ID=?
son=s07
son=s07

5件のSQLが発行してしまいますね。

◇問題
Criteriaクエリで取得したデータ(Father、Son)同時に画面に表示する場合、N+1問題が発生します。
※N+1問題:上のサンプルのように、Fatherに4件のデータがある場合、5件のSQLが発行してしまいます。

解決策:Criteria.setFetchMode


◇テストコード(抜粋)
    public static void testFetchCriteria(Session sess) {
        Criteria criteria = sess.createCriteria(Father.class);
        
        criteria.add(Restrictions.like("name", "f%"));
        criteria.setFetchMode("sonSet", FetchMode.JOIN);    //―――→①
        ...

Criteria.setFetchMode(...)メソッドでフェッチモードにFetchMode.JOINを設定します。

出力ログが次になります。
◇出力ログ
Hibernate: select this_.ID as ID2_, this_.NAME as NAME0_2_, sonset2_.FATHER_ID as FATHER3_4_, sonset2_.id as id4_, sonset2_.id as id0_, sonset2_.NAME as NAME1_0_, sonset2_.FATHER_ID as FATHER3_1_0_, father3_.ID as ID1_, father3_.NAME as NAME0_1_ from FATHER this_ left outer join SON sonset2_ on this_.ID=sonset2_.FATHER_ID left outer join FATHER father3_ on sonset2_.FATHER_ID=father3_.ID where this_.NAME like ?
==========
father: id=1 name=f01
son=s01
son=s02
==========
father: id=1 name=f01
son=s01
son=s02
==========
father: id=2 name=f02
son=s04
son=s03
==========
father: id=2 name=f02
son=s04
son=s03
==========
father: id=3 name=f03
son=s05
son=s06
==========
father: id=3 name=f03
son=s05
son=s06
==========
father: id=4 name=f04
son=s07
son=s07
==========
father: id=4 name=f04
son=s07
son=s07

SQLが1件しか発行されません。

※ここで注意いただきたいことが、そのSQL文を実行すると、次の結果になります。
ID2_     NAME0_2_     FATHER3_4_     id4_     id0_     NAME1_0_     FATHER3_1_0_     ID1_     NAME0_1_    
1     f01     1     1     1     s01     1     1     f01    
1     f01     1     2     2     s02     1     1     f01    
2     f02     2     3     3     s03     2     2     f02    
2     f02     2     4     4     s04     2     2     f02    
3     f03     3     5     5     s05     3     3     f03    
3     f03     3     6     6     s06     3     3     f03    
4     f04     4     7     7     s07     4     4     f04    
4     f04     4     8     8     s07     4     4     f04    
フェッチモードをJOINにすると、クエリの結果リスト(fatherList)のサイズは倍になってしまいます。

有关作者
Syboos.jp編集長AJavaやオープンソース情報の執筆、Webサイトの開発や運営全般の業務に携わる。

Sponsored Link


Comments

用户名 (required)

Email (will not be published) (required)

URL

Evaluation