Hibernate 入門記 パフォチュー その7 セッションキャッシュ & 問い合わせキャッシュ

今度は本編,「14.4. Managing the Session Cache」です.
save()update()saveOrUpdate() でセッションに渡したオブジェクトや,load()find()iterate()filter() で問い合わせしたオブジェクトは,セッションにキャッシュされます.これが一次キャッシュになるわけですね.
セッションキャッシュは,flush() が呼び出されると,RDB と同期されます.つまり,キャッシュされた永続オブジェクトのうち,変更されたものについては RDB が更新または挿入されます.
もし RDB と同期したくない場合は,セッションキャッシュから永続オブジェクトを破棄することができます.それには,Session

    • void evict(Object object)

を呼び出せばいいとのことです.
その際,引数で指定したオブジェクトに cascade="all" または cascade="all-delete-orphan" になっている関連があれば,それも一緒に破棄されるとのことです.
永続オブジェクトがキャッシュされているかどうかは,Session

    • boolean contains(Object object)

でチェックできるとのこと.
セッションのキャッシュを全て破棄するには,Session

    • void clear()

を呼び出せばいいそうです.


二次キャッシュを破棄するには,SessionFactory

    • void evict(Class persistentClass)
    • void evict(Class persistentClass, Serializable id)
    • void evictCollection(String roleName)
    • void evictCollection(String roleName, Serializable id)
    • void evictQueries()
    • void evictQueries(String cacheRegion)

を使うことが出来るようです.
ということなのですが,あまり興味が持てないので...
次行きますよ! 次,次!! (エビちゃん風)


次は「14.5. The Query Cache」です.
キャッシュはセッション (一次) キャッシュ・二次キャッシュだけかと思いきや,さらに問い合わせのキャッシュまであったようです.
問い合わせキャッシュは,文字通り問い合わせ結果のキャッシュで,いわゆる ResultSet キャッシュみたいなものということでしょう.
問い合わせキャッシュを有効にするには,hibernate.cache.use_query_cache というプロパティを true に設定します.
問い合わせキャッシュを使うには,Query を使用します.
デフォルトでは問い合わせキャッシュは使われないので,

    • Query setCacheable(boolean cacheable)

を引数 true で呼び出します.


ということでさっそく試してみましょう.
基本的なネタはこれまで二次キャッシュで使ってきたのと同じ.でも二次キャッシュは無効で,S2Hibernate もなし.
まずは hibernate.cfg.xml に次の行を追加.

        <property name="hibernate.cache.use_query_cache">true</property>

そして問い合わせ用のクラス.

package study;

import java.util.Iterator;
import net.sf.hibernate.Session;

public class Main {
    public static void main(String[] args) {
        try {
            HibernateTemplate template = new HibernateTemplate();

            template.process(new HibernateCallback() {
                public Object doProcess(Session session) throws Exception {
                    net.sf.hibernate.Query query = session
                            .createQuery("from study.Model model where model.name = :name");
                    query.setString("name", "Yuri Ebihara");

                    System.out.println("*** 1st query ***");
                    Iterator it = query.list().iterator();
                    while (it.hasNext()) {
                        System.out.println(it.next());
                    }

                    System.out.println("*** 2nd query ***");
                    it = query.list().iterator();
                    while (it.hasNext()) {
                        System.out.println(it.next());
                    }
                    return null;
                }
            });
        }
        catch (Exception e) {
            e.printStackTrace();
        }
    }
}

まずは問い合わせキャッシュを無効で.
こいつを実行!!

*** 1st query ***
Hibernate: select 
               model0_.id as id, model0_.name as name 
           from 
               Model model0_ 
           where 
               (model0_.name=? )
Yuri Ebihara
*** 2nd query ***
Hibernate: select 
               model0_.id as id, model0_.name as name 
           from 
               Model model0_ 
           where 
               (model0_.name=? )
Yuri Ebihara

ふむ.二回の問い合わせとも,SQL が実行されているようです.
次に,問い合わせ部分を次のように修正.

                    net.sf.hibernate.Query query = session
                            .createQuery("from study.Model model where model.name = :name");
                    query.setString("name", "Yuri Ebihara");
                    query.setCacheable(true);

最後の1行を追加しただけです.
こいつを実行!!!!

*** 1st query ***
Hibernate: select model0_.id as id, model0_.name as name from Model model0_ where (model0_.name=? )
Yuri Ebihara
*** 2nd query ***
Yuri Ebihara

ほほーーーっ.
二回目の問い合わせでは,SQL が実行されなくなったようです.いいねぇ!!


問い合わせキャッシュを破棄するには,Query

    • Query setForceCacheRefresh(boolean forceCacheRefresh)

の引数に true を渡せばいいようです.
ということで,前の実行用クラスに以下の行を追加.

                    System.out.println("*** 3rd query ***");
                    query.setForceCacheRefresh(true);
                    it = query.list().iterator();
                    while (it.hasNext()) {
                        System.out.println(it.next());
                    }

こいつを実行!!

*** 1st query ***
Hibernate: select model0_.id as id, model0_.name as name from Model model0_ where (model0_.name=? )
Yuri Ebihara
*** 2nd query ***
Yuri Ebihara
*** 3rd query ***
Hibernate: select model0_.id as id, model0_.name as name from Model model0_ where (model0_.name=? )
Yuri Ebihara

ほほーーーっ.
三回目の問い合わせでは,SQL が実行されているようです.


ちなみにこの問い合わせキャッシュ,Queryインスタンス単位でキャッシングされるわけではなく,問い合わせ文字列および問い合わせパラメータの値ごとにキャッシングされるそうです.なので,この例のように同一の Queryインスタンスを使った場合だけでなく,新しいインスタンスを作って問い合わせをした場合でも問い合わせキャッシュが有効に使われます.
また,実際にキャッシュされるのは,ID プロパティ (主キー) の値と永続クラスの型だけらしいです.永続オブジェクトそのものはセッションキャッシュまたは二次キャッシュにキャッシングされるということですね.
特に二次キャッシュとの組み合わせて使われるとのことなので,問い合わせキャッシュの寿命はセッションを超えるということでしょうか.ちょっと注意が必要かも〜.


また,問い合わせキャッシュはいくつかに分けて使うことが出来るようで,それをリージョンと呼ぶようです.
使用するリージョンを指定するには,Query

    • Query setCacheRegion(String cacheRegion)

を使います.問い合わせキャッシュの破棄は,リージョン単位で行われるようです.


それにしても,Hibernate ってキャッシュが充実していますね.
セッションキャッシュ,二次キャッシュ,そして問い合わせキャッシュ.
これらを効果的に活用できれば,RDB へのアクセスを激減することが出来るのかも?
そのための DB 設計 (おそらく,きっちり正規化して極力小さなテーブルにすべき) や,アクセス戦略 (おそらく,結合を避けて極力小さな結果セットを得るようにする) がどんなものになるかを考えるのも楽しそうです♪


さて,これで「Chapter 14. Improving performance」は終了です.
この後なのですが...
15 章はツールの説明でちょっと興味なし.心より恥じる.
16 〜 18 章はサンプルなので必要に応じて参照しましょうという感じ.
そして最後の「19. Best Practices」は興味深くはあるのですが,これも軽く目を通しておけばいいだろうということにしたいなぁ,と.
そんなわけで (どんなわけで?),長く続いた「Hibernate 入門記」も...
終〜了〜っ!!
ですっ!
いやぁ,本当に長かったなぁ.最初の「入門以前」が 05/17 なので,4か月以上ですか.まぁ,二週間で一回とかサボってた時期もありますが.心より恥じる.
ここまで続けられたのも,はてなとご愛読くださった皆様のおかげです.ありがとうございました.m(__)m