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

2008/09/05 19:27Update
TAGS: Hibernate | fetch | フェッチ戦略 | パフォーマンス | outer-join | チューニング | N+1問題

Hibernate fetchはLAZYロードにおけるN+1問題を解決します。

問題


Hibernateでは、パフォーマンスの低下を避けるために、LAZYロード(遅延ロード)という仕組みを提供しています。
※LAZYロードとは、関連するオブジェクトのロードを最初にアクセルされるまで遅延させることです。即ち、使われていないオブジェクトに、データベースへの問い合わせを行わないことになり、リソースの節約やパフォーマンスを向上します。
※Hibernate3からデフォルトでLAZYロードが有効になっています。

Hibernate究明 - Hibernate のLAZYロード 


そこで、問題が発生します。

例えば、ある画面の一覧に関連付けられたデータが同時に表示される場合、Hibernateは以下のように処理しています。
1)主たるオブジェクトを取得するために、DBにクエリを発行します。
2)LAZYロードと設定されているため、主たるオブジェクトと関連するオブジェクトの取得を遅延させます。
3)取得した一覧データを画面(JSPなど)に表示させます。
4)画面側がデータを一行ずつ処理するときに、初めて関連オブジェクトをアクセスするので、DBにクエリを発行します。
処理するデータが1万件あるとしたら、1万+1件のSQLをDBに発行してしまいます(N+1問題)。これはデータベースに大きな負担となり、システムのパフォーマンスを低下させる可能性があります。

この問題を解決するには、LAZYロードを無効にするか、他の解決策を探すしかありません。

LAZYロードを無効にしてしまうと、ほかの処理にパフォーマンスを低下させる恐れもあります。

Hibernate fetch戦略はこの問題を解決します。

fetchの値


fetchにselectやjoinを設定することができます。
◇select:外部結合を行いません。即ち、別々のSELECT文による関連しあうオブジェクトを取得します。
    例:fetch="select"
◇join:LAZYロードせず、外部結合によるDB問い合わせを行います。
    例:fetch="join"


fetchの設定


■Hibernateマッピング設定ファイル(*.hbm.xml)のmany-to-oneやmapなどの要素によって設定
◇ <many-to-one>、<many-to-many>、<one-to-one>
    例:<many-to-one ... fetch="select">

◇ <map>、<set>、<bag>、<idbag>、<list>、<array>、<primitive-array>、<join>
    例:<set ... fetch="select">
<set>と<many-to-many>などの要素ともにfetch属性を付加する場合、外側の<set>が優先されます。

■HQLクエリ時のfetch設定
例:

from Father f join fetch f.sonSet

HQLにjoin fetch を利用することで、外部結合による問い合わせを行います。

■Criteriaクエリ時のfetch設定
例:
List <Father> fatherList = (List<Father>)sess.createCriteria(Father.class).setFetchMode("sonSet", FetchMode.JOIN).list();


fetch サンプル


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    


■fetch="join"を設定しなかった場合
◇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 testFetch(Session sess) {
        List <Father> fatherList = (List<Father>)sess.createCriteria(Father.class).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_
==========
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=s04
son=s03
==========
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=s05
son=s06
==========
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

複数件のSQL文が発行されています。


■fetch="join"を指定する場合
次に、<set>にfetch="join"を指定するようにします。
◇Father.hbm.xml(抜粋)
        <set name="sonSet" cascade="all" inverse="true" fetch="join">
             <key column="FATHER_ID"/>
             <one-to-many class="test.array.Son"/>
        </set>


次のようなログが出力されます。
Hibernate: select this_.ID as ID1_, this_.NAME as NAME0_1_, sonset2_.FATHER_ID as FATHER3_3_, sonset2_.id as id3_, sonset2_.id as id0_, sonset2_.NAME as NAME1_0_, sonset2_.FATHER_ID as FATHER3_1_0_ from FATHER this_ left outer join SON sonset2_ on this_.ID=sonset2_.FATHER_ID
==========
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

1件のSQL文をしか発行されていません。

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

Sponsored Link


Comments

用户名 (required)

Email (will not be published) (required)

URL

Evaluation