1378 lines
64 KiB
XML
1378 lines
64 KiB
XML
<?xml version="1.0" encoding="Shift_JIS"?>
|
||
<chapter id="performance">
|
||
<title>パフォーマンスの改善</title>
|
||
|
||
<sect1 id="performance-fetching" revision="2">
|
||
<title>フェッチ戦略</title>
|
||
|
||
<para>
|
||
<emphasis>フェッチ戦略</emphasis> は、アプリケーションが関連をナビゲートする必要がある
|
||
ときに、Hibernateが関連オブジェクトを復元するために使用する戦略です。フェッチ戦略はO/Rマッピングの
|
||
メタデータに宣言するか、特定のHQL、 <literal>Criteria</literal> クエリでオーバーライドします。
|
||
</para>
|
||
|
||
<para>
|
||
Hibernate3は次に示すフェッチ戦略を定義しています。
|
||
</para>
|
||
|
||
<itemizedlist>
|
||
<listitem>
|
||
<para>
|
||
<emphasis>結合フェッチ</emphasis> - Hibernateは <literal>OUTER JOIN</literal> を使って、
|
||
関連するインスタンスやコレクションを1つの <literal>SELECT</literal>
|
||
で復元します。
|
||
</para>
|
||
</listitem>
|
||
<listitem>
|
||
<para>
|
||
<emphasis>セレクトフェッチ</emphasis> - 2回目の <literal>SELECT</literal>
|
||
で関連するエンティティやコレクションを復元します。 <literal>lazy="false"</literal>
|
||
で明示的に遅延フェッチを無効にしなければ、この2回目のselectは実際に
|
||
関連にアクセスしたときのみ実行されるでしょう。
|
||
</para>
|
||
</listitem>
|
||
<listitem>
|
||
<para>
|
||
<emphasis>サブセレクトフェッチ</emphasis> - 2回目の <literal>SELECT</literal>
|
||
で、直前のクエリやフェッチで復元したすべての要素に関連するコレクションを
|
||
復元します。 <literal>lazy="false"</literal>
|
||
で明示的に遅延フェッチを無効にしなければ、この2回目のselectは実際に
|
||
関連にアクセスしたときのみ実行されるでしょう。
|
||
</para>
|
||
</listitem>
|
||
<listitem>
|
||
<para>
|
||
<emphasis>バッチフェッチ</emphasis> - セレクトフェッチのための最適化された戦略
|
||
- Hibernateはエンティティのインスタンスやコレクションの一群を1回の
|
||
<literal>SELECT</literal> で復元します。これは主キーや外部キーのリストを指定することに
|
||
により行います。
|
||
</para>
|
||
</listitem>
|
||
</itemizedlist>
|
||
|
||
<para>
|
||
Hibernateは次に示す戦略とも区別をします。
|
||
</para>
|
||
|
||
<itemizedlist>
|
||
<listitem>
|
||
<para>
|
||
<emphasis>即時フェッチ</emphasis> - 所有者のオブジェクトがロードされたときに、
|
||
関連、コレクションは即時にフェッチされます。
|
||
</para>
|
||
</listitem>
|
||
<listitem>
|
||
<para>
|
||
<emphasis>遅延コレクションフェッチ</emphasis> - アプリケーションがコレクションに
|
||
対して操作を行ったときにコレクションをフェッチします。
|
||
(これはコレクションに対するデフォルトの動作です)
|
||
</para>
|
||
</listitem>
|
||
<listitem>
|
||
<para>
|
||
<emphasis>"特別な遅延"コレクションフェッチ</emphasis> - コレクションの要素
|
||
1つ1つが独立して、必要なときにデータベースから取得されます。
|
||
Hibernateは必要ないならば、コレクション全体をメモリにフェッチすることは
|
||
避けます(とても大きなコレクションに適しています)。
|
||
</para>
|
||
</listitem>
|
||
<listitem>
|
||
<para>
|
||
<emphasis>プロキシフェッチ</emphasis> - 単一値関連は、識別子のgetter以外の
|
||
メソッドが関連オブジェクトで呼び出されるときにフェッチされます。
|
||
</para>
|
||
</listitem>
|
||
<listitem>
|
||
<para>
|
||
<emphasis>"プロキシなし"フェッチ</emphasis> - 単一値関連は、インスタンス変数に
|
||
アクセスされたときにフェッチされます。プロキシフェッチと比較すると、この方法は
|
||
遅延の度合いが少ない(関連は識別子にアクセスしただけでもフェッチされます)
|
||
ですが、より透過的で、アプリケーションにプロキシが存在しないように見せます。
|
||
この方法はビルド時のバイトコード組み込みが必要になり、使う場面はまれです。
|
||
</para>
|
||
</listitem>
|
||
<listitem>
|
||
<para>
|
||
<emphasis>遅延属性フェッチ</emphasis> - 属性や単一値関連は、インスタンス変数にアクセスした
|
||
ときにフェッチされます。この方法はビルド時のバイトコード組み込みが必要になり、
|
||
使う場面はまれです。
|
||
</para>
|
||
</listitem>
|
||
</itemizedlist>
|
||
|
||
<para>
|
||
二つの直行する概念があります: <emphasis>いつ</emphasis> 関連をフェッチするか、
|
||
そして、 <emphasis>どうやって</emphasis> フェッチするか(どんなSQLを使って)。
|
||
これらを混同しないでください! <literal>fetch</literal> はパフォーマンスチューニングに使います。
|
||
<literal>lazy</literal> はあるクラスの分離されたインスタンスのうち、どのデータを常に
|
||
使用可能にするかの取り決めを定義します。
|
||
</para>
|
||
|
||
<sect2 id="performance-fetching-lazy">
|
||
<title>遅延関連の働き</title>
|
||
|
||
<para>
|
||
デフォルトでは、Hibernate3はコレクションに対しては遅延セレクトフェッチを使い、
|
||
単一値関連には遅延プロキシフェッチを使います。これらのデフォルト動作はほぼすべての
|
||
アプリケーションのほぼすべての関連で意味があります。
|
||
</para>
|
||
|
||
<para>
|
||
<emphasis>注:</emphasis>
|
||
<literal>hibernate.default_batch_fetch_size</literal> をセットしたときは、Hibernateは
|
||
遅延フェッチのためのバッチフェッチ最適化を使うでしょう
|
||
(この最適化はより細かいレベルで有効にすることも出来ます)。
|
||
</para>
|
||
|
||
<para>
|
||
しかし、遅延フェッチは知っておかなければならない一つの問題があります。
|
||
Hibernateのsessionをオープンしているコンテキストの外から遅延関連にアクセスすると、
|
||
例外が発生します。例:
|
||
</para>
|
||
|
||
<programlisting><![CDATA[s = sessions.openSession();
|
||
Transaction tx = s.beginTransaction();
|
||
|
||
User u = (User) s.createQuery("from User u where u.name=:userName")
|
||
.setString("userName", userName).uniqueResult();
|
||
Map permissions = u.getPermissions();
|
||
|
||
tx.commit();
|
||
s.close();
|
||
|
||
Integer accessLevel = (Integer) permissions.get("accounts"); // Error!]]></programlisting>
|
||
|
||
<para>
|
||
<literal>Session</literal> がクローズされたとき、permissionsコレクションは
|
||
初期化されていないため、このコレクションは自身の状態をロードできません。
|
||
<emphasis>Hibernateは切り離されたオブジェクトの遅延初期化はサポート
|
||
していません</emphasis> 。修正方法として、コレクションから読み込みを行うコードを
|
||
トランザクションをコミットする直前に移動させます。
|
||
</para>
|
||
|
||
<para>
|
||
一方で、 <literal>lazy="false"</literal> を関連マッピングに指定することで、
|
||
遅延処理をしないコレクションや関連を使うことが出来ます。
|
||
しかしながら、遅延初期化はほぼすべてのコレクションや関連で使われることを
|
||
意図しています。もしあなたのオブジェクトモデルの中に遅延処理をしない関連を
|
||
多く定義してしまうと、Hibernateは最終的にはトランザクション毎に
|
||
ほぼ完全なデータベースをメモリの中にフェッチすることになるでしょう!
|
||
</para>
|
||
|
||
<para>
|
||
他方では、特定のトランザクションにおいてセレクトフェッチの代わりに
|
||
結合フェッチ(当然これは遅延処理ではなくなります)を選択したいことが
|
||
時々あります。これからフェッチ戦略をカスタマイズする方法をお見せします。
|
||
Hibernate3では、フェッチ戦略を選択する仕組みは単一値関連とコレクションで
|
||
変わりはありません。
|
||
</para>
|
||
|
||
</sect2>
|
||
|
||
<sect2 id="performance-fetching-custom" revision="4">
|
||
<title>フェッチ戦略のチューニング</title>
|
||
|
||
<para>
|
||
セレクトフェッチ(デフォルト)はN+1セレクト問題という大きな弱点があるため、
|
||
マッピング定義で結合フェッチを有効にすることができます。
|
||
</para>
|
||
|
||
<programlisting><![CDATA[<set name="permissions"
|
||
fetch="join">
|
||
<key column="userId"/>
|
||
<one-to-many class="Permission"/>
|
||
</set]]></programlisting>
|
||
|
||
<programlisting><![CDATA[<many-to-one name="mother" class="Cat" fetch="join"/>]]></programlisting>
|
||
|
||
<para>
|
||
マッピング定義で定義した <literal>フェッチ</literal> 戦略は次のものに影響します。
|
||
</para>
|
||
|
||
<itemizedlist>
|
||
<listitem>
|
||
<para>
|
||
<literal>get()</literal> や <literal>load()</literal> による復元
|
||
</para>
|
||
</listitem>
|
||
<listitem>
|
||
<para>
|
||
|
||
関連にナビゲートしたときに発生する暗黙的な復元
|
||
</para>
|
||
</listitem>
|
||
<listitem>
|
||
<para>
|
||
<literal>Criteria</literal> クエリ
|
||
</para>
|
||
</listitem>
|
||
<listitem>
|
||
<para>
|
||
<literal>サブセレクト</literal> フェッチを使うHQLクエリ
|
||
</para>
|
||
</listitem>
|
||
</itemizedlist>
|
||
|
||
<para>
|
||
たとえどんなフェッチ戦略を使ったとしても、遅延ではないグラフはメモリに読み込まれることが
|
||
保証されます。つまり、特定のHQLクエリを実行するためにいくつかのSELECT文が即時実行される
|
||
ことがあるので注意してください。
|
||
</para>
|
||
|
||
<para>
|
||
通常は、マッピング定義でフェッチのカスタマイズは行いません。
|
||
代わりに、デフォルトの動作のままにしておいて、HQLで <literal>left join fetch</literal> を
|
||
指定することで特定のトランザクションで動作をオーバーライドします。
|
||
これはHibernateに初回のセレクトで外部結合を使って関連を先にフェッチするように指定しています。
|
||
<literal>Criteria</literal> クエリのAPIでは、
|
||
<literal>setFetchMode(FetchMode.JOIN)</literal> を使うことが出来ます。
|
||
</para>
|
||
|
||
<para>
|
||
もし <literal>get()</literal> や <literal>load()</literal> で使われる
|
||
フェッチ戦略を変えたいと感じたときには、単純に
|
||
<literal>Criteria</literal> クエリを使ってください。例:
|
||
</para>
|
||
|
||
<programlisting><![CDATA[User user = (User) session.createCriteria(User.class)
|
||
.setFetchMode("permissions", FetchMode.JOIN)
|
||
.add( Restrictions.idEq(userId) )
|
||
.uniqueResult();]]></programlisting>
|
||
|
||
<para>
|
||
(これはいくつかのORMソリューションが"fetch plan"と呼んでいるものと同じです。)
|
||
</para>
|
||
|
||
<para>
|
||
N+1セレクト問題を避けるためのまったく違う方法は、第2レベルキャッシュを使うことです。
|
||
</para>
|
||
|
||
</sect2>
|
||
|
||
<sect2 id="performance-fetching-proxies" revision="2">
|
||
<title>単一端関連プロキシ</title>
|
||
|
||
<para>
|
||
コレクションの遅延フェッチは、Hibernate自身の実装による永続コレクションを使って
|
||
実現しています。しかし、単一端関連における遅延処理では、違う仕組みが
|
||
必要です。対象の関連エンティティはプロキシでなければなりません。Hibernateは
|
||
(すばらしいCGLIBライブラリによる)実行時のバイトコード拡張を
|
||
使って永続オブジェクトの遅延初期化プロキシを実現しています。
|
||
</para>
|
||
|
||
<para>
|
||
デフォルトでは、Hibernate3は(開始時に)すべての永続クラスのプロキシを生成し、
|
||
それらを使って、 <literal>many-to-one</literal> や <literal>one-to-one</literal> 関連の
|
||
遅延フェッチを可能にしています。
|
||
</para>
|
||
|
||
<para>
|
||
マッピングファイルで <literal>proxy</literal> 属性によって、クラスのプロキシインターフェイスとして
|
||
使うインターフェイスを宣言できます。デフォルトでは、Hibernateはそのクラスのサブクラスを使います。
|
||
<emphasis>プロキシクラスは少なくともパッケージ可視でデフォルトコンストラクタを実装しなければ
|
||
ならないことに注意してください。すべての永続クラスにこのコンストラクタを推奨します!</emphasis>
|
||
</para>
|
||
|
||
<para>
|
||
ポリモーフィズムのクラスに対してこの方法を適用するときにいくつか考慮することがあります。
|
||
例:
|
||
</para>
|
||
|
||
<programlisting><![CDATA[<class name="Cat" proxy="Cat">
|
||
......
|
||
<subclass name="DomesticCat">
|
||
.....
|
||
</subclass>
|
||
</class>]]></programlisting>
|
||
|
||
<para>
|
||
第一に、 <literal>Cat</literal> のインスタンスは <literal>DomesticCat</literal>
|
||
にキャストできません。たとえ基となるインスタンスが <literal>DomesticCat</literal>
|
||
であったとしてもです。
|
||
</para>
|
||
|
||
<programlisting><![CDATA[Cat cat = (Cat) session.load(Cat.class, id); // instantiate a proxy (does not hit the db)
|
||
if ( cat.isDomesticCat() ) { // hit the db to initialize the proxy
|
||
DomesticCat dc = (DomesticCat) cat; // Error!
|
||
....
|
||
}]]></programlisting>
|
||
|
||
<para>
|
||
第二に、プロキシの <literal>==</literal> は成立しないことがあります。
|
||
</para>
|
||
|
||
<programlisting><![CDATA[Cat cat = (Cat) session.load(Cat.class, id); // instantiate a Cat proxy
|
||
DomesticCat dc =
|
||
(DomesticCat) session.load(DomesticCat.class, id); // acquire new DomesticCat proxy!
|
||
System.out.println(cat==dc); // false]]></programlisting>
|
||
|
||
<para>
|
||
しかし、これは見かけほど悪い状況というわけではありません。たとえ異なったプロキシオブジェクトへの
|
||
二つの参照があったとしても、基となるインスタンスは同じオブジェクトです。
|
||
</para>
|
||
|
||
<programlisting><![CDATA[cat.setWeight(11.0); // hit the db to initialize the proxy
|
||
System.out.println( dc.getWeight() ); // 11.0]]></programlisting>
|
||
|
||
<para>
|
||
第三に、 <literal>final</literal> クラスや <literal>final</literal> メソッドを持つクラスに
|
||
CGLIBプロキシを使えません。
|
||
</para>
|
||
|
||
<para>
|
||
最後に、もし永続オブジェクトのインスタンス化時(例えば、初期化処理やデフォルトコンストラクタの中で)
|
||
になんらかのリソースが必要となるなら、そのリソースもまたプロキシを通して取得されます。
|
||
実際には、プロキシクラスは永続クラスのサブクラスです。
|
||
</para>
|
||
|
||
<para>
|
||
これらの問題はJavaの単一継承モデルの原理上の制限のためです。もしこれらの問題を避けたいのなら、
|
||
ビジネスメソッドを宣言したインターフェイスをそれぞれ永続クラスで実装しなければなりません。
|
||
マッピングファイルでこれらのインターフェイスを指定する必要があります。例:
|
||
</para>
|
||
|
||
<programlisting><![CDATA[<class name="CatImpl" proxy="Cat">
|
||
......
|
||
<subclass name="DomesticCatImpl" proxy="DomesticCat">
|
||
.....
|
||
</subclass>
|
||
</class>]]></programlisting>
|
||
|
||
<para>
|
||
<literal>CatImpl</literal> は <literal>Cat</literal> インターフェイスを実装するのに対し、
|
||
<literal>DomesticCatImpl</literal> は <literal>DomesticCat</literal> を実装します。
|
||
すると、 <literal>load()</literal> や <literal>iterate()</literal> は、
|
||
<literal>Cat</literal> や <literal>DomesticCat</literal> のインスタンスのプロキシを
|
||
返します。( <literal>list()</literal> は通常はプロキシを返さないことに注意してください。)
|
||
</para>
|
||
|
||
<programlisting><![CDATA[Cat cat = (Cat) session.load(CatImpl.class, catid);
|
||
Iterator iter = session.iterate("from CatImpl as cat where cat.name='fritz'");
|
||
Cat fritz = (Cat) iter.next();]]></programlisting>
|
||
|
||
<para>
|
||
関連も遅延初期化されます。これはプロパティを <literal>Cat</literal> 型で宣言しなければ
|
||
ならないことを意味します。 <literal>CatImpl</literal> ではありません。
|
||
</para>
|
||
|
||
<para>
|
||
プロキシの初期化を <emphasis>必要としない</emphasis> 操作も存在します。
|
||
</para>
|
||
|
||
<itemizedlist spacing="compact">
|
||
<listitem>
|
||
<para>
|
||
<literal>equals()</literal> (永続クラスが <literal>equals()</literal> を
|
||
オーバーライドしないとき)
|
||
</para>
|
||
</listitem>
|
||
<listitem>
|
||
<para>
|
||
<literal>hashCode()</literal> (永続クラスが <literal>hashCode()</literal> を
|
||
オーバーライドしないとき)
|
||
</para>
|
||
</listitem>
|
||
<listitem>
|
||
<para>
|
||
識別子のgetterメソッド
|
||
</para>
|
||
</listitem>
|
||
</itemizedlist>
|
||
|
||
<para>
|
||
Hibernateは <literal>equals()</literal> や <literal>hashCode()</literal> をオーバーライドした
|
||
永続クラスを検出します。
|
||
</para>
|
||
|
||
<para>
|
||
デフォルトの <literal>lazy="proxy"</literal> の代わりに、 <literal>lazy="no-proxy"</literal> を
|
||
選んだことで、型変換に関連する問題を回避することが出来ます。
|
||
しかし、ビルド時のバイトコード組み込みが必要になり、どのような操作であっても、
|
||
ただちにプロキシの初期化を行うことになるでしょう。
|
||
</para>
|
||
|
||
</sect2>
|
||
|
||
<sect2 id="performance-fetching-initialization" revision="1">
|
||
<title>コレクションとプロキシの初期化</title>
|
||
|
||
<para>
|
||
<literal>LazyInitializationException</literal> は、 <literal>Session</literal> のスコープ外から
|
||
初期化していないコレクションやプロキシにアクセスされたときに、Hibernateによってスローされます。
|
||
すなわち、コレクションやプロキシへの参照を持つエンティティが分離された状態の時です。
|
||
</para>
|
||
|
||
<para>
|
||
<literal>Session</literal> をクローズする前にプロキシやコレクションの初期化を確実に
|
||
行いたいときがあります。もちろん、 <literal>cat.getSex()</literal>
|
||
や <literal>cat.getKittens().size()</literal> などを常に呼び出すことで初期化を強制することはできます。
|
||
しかしこれはコードを読む人を混乱させ、汎用的なコードという点からも不便です。
|
||
</para>
|
||
|
||
<para>
|
||
staticメソッドの <literal>Hibernate.initialize()</literal> や <literal>Hibernate.isInitialized()</literal>
|
||
は遅延初期化のコレクションやプロキシを扱うときに便利な方法をアプリケーションに提供します。
|
||
<literal>Hibernate.initialize(cat)</literal> は、 <literal>Session</literal> がオープンしている限りは
|
||
<literal>cat</literal> プロキシを強制的に初期化します。
|
||
<literal>Hibernate.initialize( cat.getKittens() )</literal> はkittensコレクションに対して同様の
|
||
効果があります。
|
||
</para>
|
||
|
||
<para>
|
||
別の選択肢として、必要なすべてのコレクションやプロキシがロードされるまで
|
||
<literal>Session</literal> をオープンにしておく方法があります。いくつかのアプリケーションの
|
||
アーキテクチャでは、特にHibernateによるデータアクセスを行うコードと、それを使う
|
||
コードが異なるアプリケーションのレイヤーや、物理的に異なるプロセッサのときには、
|
||
コレクションが初期化されるときに <literal>Session</literal> がオープンしていることを
|
||
保証する問題があります。この問題に対しては2つの基本的な方法があります。
|
||
</para>
|
||
|
||
<itemizedlist>
|
||
<listitem>
|
||
<para>
|
||
|
||
Webベースのアプリケーションでは、
|
||
ビューのレンダリングが完了し、リクエストが終わる一番最後で <literal>Session</literal>
|
||
をクローズするために、サーブレットフィルタを使うことができます( <emphasis>Open Session in View</emphasis>
|
||
パターンです)。もちろん、アプリケーション基盤の例外処理の正確性が非常に重要になります。
|
||
ビューのレンダリング中に例外が発生したときでさえ、ユーザに処理が戻る前に
|
||
<literal>Session</literal> のクローズとトランザクションの終了を行う
|
||
ことが不可欠になります。
|
||
HibernateのWikiに載っている"Open Session in View"パターンの例を参照してください。
|
||
</para>
|
||
</listitem>
|
||
<listitem>
|
||
<para>
|
||
ビジネス層が分離しているアプリケーションでは、ビジネスロジックは
|
||
Web層で必要になるすべてのコレクションを事前に"準備"する必要があります。
|
||
これは特定のユースケースで必要となるプレゼンテーション/Web層に対し、
|
||
ビジネス層がすべてのデータをロードし、すべてのデータを初期化して返すべきと
|
||
いうことを意味しています。通常は、アプリケーションはWeb層で必要なコレクション
|
||
それぞれに対して <literal>Hibernate.initialize()</literal> を呼び出すか
|
||
(この呼び出しはセッションをクローズする前に行う必要があります)、
|
||
Hibernateクエリの <literal>FETCH</literal> 節や <literal>Criteria</literal>
|
||
の <literal>FetchMode.JOIN</literal> を使ってコレクションを先に復元します。
|
||
普通は <emphasis>Session Facade</emphasis> パターンの代わりに
|
||
<emphasis>Command</emphasis> パターンを採用するほうがより簡単です。
|
||
</para>
|
||
</listitem>
|
||
<listitem>
|
||
<para>
|
||
初期化されていないコレクション(もしくは他のプロキシ)にアクセスする前に、
|
||
<literal>merge()</literal> や <literal>lock()</literal> を使って新しい
|
||
<literal>Session</literal> に以前にロードされたオブジェクトを追加することも出来ます。
|
||
アドホックなトランザクションのセマンティクスを導入したので、Hibernateは
|
||
これを自動的に行わず、 <emphasis>行うべきでもありません</emphasis> !
|
||
</para>
|
||
</listitem>
|
||
</itemizedlist>
|
||
|
||
<para>
|
||
大きなコレクションを初期化したくはないが、コレクションについてのなんらかの情報(サイズのような)
|
||
やデータのサブセットを必要とすることがあります。
|
||
</para>
|
||
|
||
<para>
|
||
コレクションフィルタを使うことで、初期化せずにコレクションのサイズを取得することが出来ます。
|
||
</para>
|
||
|
||
<programlisting><![CDATA[( (Integer) s.createFilter( collection, "select count(*)" ).list().get(0) ).intValue()]]></programlisting>
|
||
|
||
<para>
|
||
<literal>createFilter()</literal> メソッドは、コレクション全体を初期化する必要なしに、コレクションの
|
||
サブセットを復元するために効果的に使えます。
|
||
</para>
|
||
|
||
<programlisting><![CDATA[s.createFilter( lazyCollection, "").setFirstResult(0).setMaxResults(10).list();]]></programlisting>
|
||
|
||
</sect2>
|
||
|
||
<sect2 id="performance-fetching-batch">
|
||
<title>バッチフェッチの使用</title>
|
||
|
||
<para>
|
||
Hibernateはバッチフェッチを効率的に使用できます。一つのプロキシ(もしくはコレクション)がアクセス
|
||
されると、Hibernateはいくつかの初期化していないプロキシをロードすることができます。バッチフェッチは
|
||
遅延セレクトフェッチ戦略に対する最適化です。バッチフェッチの調整には2つの方法があります。
|
||
クラスレベルとコレクションレベルです。
|
||
</para>
|
||
|
||
<para>
|
||
クラス、要素のバッチフェッチは理解が簡単です。実行時の次の場面を想像してください。
|
||
<literal>Session</literal> にロードされた25個の <literal>Cat</literal> インスタンスが存在し、
|
||
それぞれの <literal>Cat</literal> は <literal>owner</literal> である <literal>Person</literal> への関連を持ちます。
|
||
<literal>Person</literal> クラスは <literal>lazy="true"</literal> のプロキシでマッピングされています。
|
||
もし今すべてのCatに対して繰り返し <literal>getOwner()</literal> を呼び出すと、Hibernateは
|
||
デフォルトでは25回の <literal>SELECT</literal> を実行し、ownerプロキシの復元をします。
|
||
この振る舞いを <literal>Person</literal> のマッピングの <literal>batch-size</literal>
|
||
の指定で調整できます。
|
||
</para>
|
||
|
||
<programlisting><![CDATA[<class name="Person" batch-size="10">...</class>]]></programlisting>
|
||
|
||
<para>
|
||
Hibernateはクエリを3回だけを実行するようになります。パターンは10, 10, 5です。
|
||
</para>
|
||
|
||
<para>
|
||
コレクションのバッチフェッチも有効にすることが出来ます。例として、それぞれの
|
||
<literal>Person</literal> が <literal>Cat</literal> の遅延コレクションを持っており、
|
||
10個のPersonが <literal>Sesssion</literal> にロードされたとすると、すべてのPersonに
|
||
対して繰り返し <literal>getCats()</literal> を呼び出すことで、計10回の <literal>SELECT</literal>
|
||
が発生します。もし <literal>Person</literal> のマッピングで <literal>cats</literal>
|
||
コレクションのバッチフェッチを有効にすれば、Hibernateはコレクションの事前フェッチが出来ます。
|
||
</para>
|
||
|
||
<programlisting><![CDATA[<class name="Person">
|
||
<set name="cats" batch-size="3">
|
||
...
|
||
</set>
|
||
</class>]]></programlisting>
|
||
|
||
<para>
|
||
<literal>batch-size</literal> が3なので、Hibernateは4回の <literal>SELECT</literal>
|
||
で3個、3個、3個、1個をロードします。繰り返すと、属性の値は特定の <literal>Session</literal>
|
||
の中の初期化されていないコレクションの期待数に依存します。
|
||
</para>
|
||
|
||
<para>
|
||
コレクションのバッチフェッチはアイテムのネストしたツリー、 すなわち、代表的な部品表のパターンが
|
||
ある場合に特に有用です。(しかし、読み込みが多いツリーでは <emphasis>ネストしたset</emphasis>
|
||
や <emphasis>具体化したパス</emphasis> がよりよい選択になります。)
|
||
</para>
|
||
|
||
</sect2>
|
||
|
||
<sect2 id="performance-fetching-subselect">
|
||
<title>サブセレクトフェッチの使用</title>
|
||
|
||
<para>
|
||
一つの遅延コレクションや単一値プロキシがフェッチされなければいけないとき、Hibernateは
|
||
それらすべてをロードし、サブセレクトのオリジナルクエリが再度実行されます。これは
|
||
バッチフェッチと同じ方法で動き、少しずつのロードは行いません。
|
||
</para>
|
||
|
||
<!-- TODO: Write more about this -->
|
||
<!-- 早く書けよ -->
|
||
</sect2>
|
||
|
||
<sect2 id="performance-fetching-lazyproperties">
|
||
<title>遅延プロパティフェッチの使用</title>
|
||
|
||
<para>
|
||
Hibernate3はプロパティごとの遅延フェッチをサポートしています。この最適化手法は
|
||
<emphasis>グループのフェッチ</emphasis> としても知られています。これはほとんど
|
||
要望から出た機能であることに注意してください。実際には列読み込みの最適化よりも、
|
||
行読み込みの最適化が非常に重要です。
|
||
しかし、クラスのいくつかのプロパティだけを読み込むことは、既存のテーブルが何百もの列を持ち、
|
||
データモデルを改善できないなどの極端な場合には有用です。
|
||
</para>
|
||
|
||
<para>
|
||
遅延プロパティ読み込みを有効にするには、対象のプロパティのマッピングで <literal>lazy</literal>
|
||
属性をセットしてください。
|
||
</para>
|
||
|
||
<programlisting><![CDATA[<class name="Document">
|
||
<id name="id">
|
||
<generator class="native"/>
|
||
</id>
|
||
<property name="name" not-null="true" length="50"/>
|
||
<property name="summary" not-null="true" length="200" lazy="true"/>
|
||
<property name="text" not-null="true" length="2000" lazy="true"/>
|
||
</class>]]></programlisting>
|
||
|
||
<para>
|
||
遅延プロパティ読み込みはビルド時のバイトコード組み込みを必要とします!もし
|
||
永続クラスに組み込みがされていないなら、Hibernateは黙って遅延プロパティの設定を無視して、
|
||
即時フェッチに戻します。
|
||
</para>
|
||
|
||
<para>
|
||
バイトコード組み込みは以下のAntタスクを使ってください。
|
||
</para>
|
||
|
||
<programlisting><![CDATA[<target name="instrument" depends="compile">
|
||
<taskdef name="instrument" classname="org.hibernate.tool.instrument.InstrumentTask">
|
||
<classpath path="${jar.path}"/>
|
||
<classpath path="${classes.dir}"/>
|
||
<classpath refid="lib.class.path"/>
|
||
</taskdef>
|
||
|
||
<instrument verbose="true">
|
||
<fileset dir="${testclasses.dir}/org/hibernate/auction/model">
|
||
<include name="*.class"/>
|
||
</fileset>
|
||
</instrument>
|
||
</target>]]></programlisting>
|
||
|
||
<para>
|
||
不要な列を読み込まないための、別の(よりよい?)方法は、少なくとも
|
||
読み込みのみのトランザクションにおいては、HQLやCriteriaクエリの射影
|
||
機能を使うことです。この方法はビルド時のバイトコード組み込みが不要になり、
|
||
より良い解決方法です。
|
||
</para>
|
||
|
||
<para>
|
||
HQLで <literal>fetch all properties</literal> を使うことで、普通どおりの
|
||
プロパティの即時フェッチングを強制することが出来ます。
|
||
</para>
|
||
|
||
</sect2>
|
||
|
||
</sect1>
|
||
|
||
<sect1 id="performance-cache" revision="1">
|
||
<title>第2レベルキャッシュ</title>
|
||
|
||
<para>
|
||
Hibernateの <literal>Session</literal> は永続データのトランザクションレベルのキャッシュです。
|
||
class-by-classとcollection-by-collectionごとの、クラスタレベルやJVMレベル
|
||
( <literal>SessionFactory</literal> レベル)のキャッシュを設定することが出来ます。
|
||
クラスタ化されたキャッシュにつなぐことさえ出来ます。しかし注意してください。
|
||
キャッシュは他のアプリケーションによる永続層の変更を
|
||
考慮しません(キャッシュデータを定期的に期限切れにする設定は出来ます)。
|
||
</para>
|
||
|
||
<para revision="1">
|
||
Hibernateが使用するキャッシュ実装は、<literal>hibernate.cache.provider_class</literal> プロパティに
|
||
<literal>org.hibernate.cache.CacheProvider</literal> を実装したクラス名を指定することで変更できます。
|
||
Hibernateは多くのオープンソースのキャッシュプロバイダをビルトイン実装で持っています(後にリストがあります)。
|
||
加えて、前に説明したように、あなた自身が独自の実装をして、それを組み込むことも出来ます。
|
||
バージョン3.2より前ではEhCacheがデフォルトのキャッシュプロバイダであることに注意してください。
|
||
バージョン3.2ではこれは当てはまりません。
|
||
</para>
|
||
|
||
<table frame="topbot" id="cacheproviders" revision="1">
|
||
<title>キャッシュプロバイダ</title>
|
||
<tgroup cols='5' align='left' colsep='1' rowsep='1'>
|
||
<colspec colname='c1' colwidth="1*"/>
|
||
<colspec colname='c2' colwidth="3*"/>
|
||
<colspec colname='c3' colwidth="1*"/>
|
||
<colspec colname='c4' colwidth="1*"/>
|
||
<colspec colname='c5' colwidth="1*"/>
|
||
<thead>
|
||
<row>
|
||
<entry>キャッシュ</entry>
|
||
<entry>プロバイダクラス</entry>
|
||
<entry>タイプ</entry>
|
||
<entry>クラスタセーフ</entry>
|
||
<entry>クエリキャッシュのサポート</entry>
|
||
</row>
|
||
</thead>
|
||
<tbody>
|
||
<row>
|
||
<entry>Hashtable(製品用として意図していません)</entry>
|
||
<entry><literal>org.hibernate.cache.HashtableCacheProvider</literal></entry>
|
||
<entry>メモリ</entry>
|
||
<entry></entry>
|
||
<entry>yes</entry>
|
||
</row>
|
||
<row>
|
||
<entry>EHCache</entry>
|
||
<entry><literal>org.hibernate.cache.EhCacheProvider</literal></entry>
|
||
<entry>メモリ、ディスク</entry>
|
||
<entry></entry>
|
||
<entry>yes</entry>
|
||
</row>
|
||
<row>
|
||
<entry>OSCache</entry>
|
||
<entry><literal>org.hibernate.cache.OSCacheProvider</literal></entry>
|
||
<entry>メモリ、ディスク</entry>
|
||
<entry></entry>
|
||
<entry>yes</entry>
|
||
</row>
|
||
<row>
|
||
<entry>SwarmCache</entry>
|
||
<entry><literal>org.hibernate.cache.SwarmCacheProvider</literal></entry>
|
||
<entry>クラスタ(ipマルチキャスト)</entry>
|
||
<entry>yes(クラスタ無効化)</entry>
|
||
<entry></entry>
|
||
</row>
|
||
<row>
|
||
<entry>JBoss TreeCache</entry>
|
||
<entry><literal>org.hibernate.cache.TreeCacheProvider</literal></entry>
|
||
<entry>クラスタ(ipマルチキャスト)、トランザクショナル</entry>
|
||
<entry>yes(複製)</entry>
|
||
<entry>yes(時刻同期が必要)</entry>
|
||
</row>
|
||
</tbody>
|
||
</tgroup>
|
||
</table>
|
||
|
||
<sect2 id="performance-cache-mapping" revision="2">
|
||
<title>キャッシュのマッピング</title>
|
||
|
||
<para>
|
||
クラスやコレクションのマッピングの <literal><cache></literal> 要素は以下の形式です。
|
||
</para>
|
||
|
||
<programlistingco>
|
||
<areaspec>
|
||
<area id="cache1" coords="2 70"/>
|
||
<area id="cache2" coords="3 70"/>
|
||
<area id="cache3" coords="4 70"/>
|
||
</areaspec>
|
||
<programlisting><![CDATA[<cache
|
||
usage="transactional|read-write|nonstrict-read-write|read-only"
|
||
region="RegionName"
|
||
include="all|non-lazy"
|
||
/>]]></programlisting>
|
||
<calloutlist>
|
||
<callout arearefs="cache1">
|
||
<para>
|
||
<literal>usage</literal> (required) specifies the caching strategy:
|
||
<literal>transactional</literal>,
|
||
<literal>read-write</literal>,
|
||
<literal>nonstrict-read-write</literal> or
|
||
<literal>read-only</literal>
|
||
</para>
|
||
</callout>
|
||
<callout arearefs="cache2">
|
||
<para>
|
||
<literal>region</literal> (optional, defaults to the class or
|
||
collection role name) specifies the name of the second level cache
|
||
region
|
||
</para>
|
||
</callout>
|
||
<callout arearefs="cache3">
|
||
<para>
|
||
<literal>include</literal> (optional, defaults to <literal>all</literal>)
|
||
<literal>non-lazy</literal> specifies that properties of the entity mapped
|
||
with <literal>lazy="true"</literal> may not be cached when attribute-level
|
||
lazy fetching is enabled
|
||
</para>
|
||
</callout>
|
||
</calloutlist>
|
||
</programlistingco>
|
||
|
||
<para>
|
||
または(よりよい方法として?)、 <literal>hibernate.cfg.xml</literal> に <literal><class-cache></literal>
|
||
と <literal><collection-cache></literal> 要素を指定することも出来ます。
|
||
</para>
|
||
|
||
<para>
|
||
<literal>usage</literal> 属性は <emphasis>キャッシュの並列性戦略</emphasis> を指定します。
|
||
</para>
|
||
|
||
</sect2>
|
||
|
||
<sect2 id="performance-cache-readonly">
|
||
<title>read only戦略</title>
|
||
|
||
<para>
|
||
もしアプリケーションが読み込みのみ必要で、永続クラスのインスタンスを変更しないなら、
|
||
<literal>read-only</literal> キャッシュを使うことが出来ます。これはもっとも単純で
|
||
もっともパフォーマンスの良い戦略です。クラスタでの使用も完全に安全です。
|
||
</para>
|
||
|
||
<programlisting><![CDATA[<class name="eg.Immutable" mutable="false">
|
||
<cache usage="read-only"/>
|
||
....
|
||
</class>]]></programlisting>
|
||
|
||
</sect2>
|
||
|
||
|
||
<sect2 id="performance-cache-readwrite">
|
||
<title>read/write戦略</title>
|
||
|
||
<para>
|
||
アプリケーションがデータを更新する必要があるなら、 <literal>read-write</literal> キャッシュが適当かも
|
||
しれません。このキャッシュ戦略は、シリアライザブルなトランザクション分離レベルが要求されるなら、
|
||
決して使うべきではありません。もしキャッシュがJTA環境で使われるなら、JTA <literal>TransactionManager</literal>
|
||
を取得するための方法を示す <literal>hibernate.transaction.manager_lookup_class</literal>
|
||
プロパティを指定しなければなりません。他の環境では、 <literal>Session.close()</literal>
|
||
や <literal>Session.disconnect()</literal> が呼ばれたときに、確実にトランザクションが完了
|
||
していなければなりません。
|
||
もしクラスタでこの戦略を使いたいなら、基となるキャッシュの実装がロックをサポート
|
||
していることを保証しなければなりません。
|
||
組み込みのキャッシュプロバイダは <emphasis>サポートしていません</emphasis> 。
|
||
</para>
|
||
|
||
<programlisting><![CDATA[<class name="eg.Cat" .... >
|
||
<cache usage="read-write"/>
|
||
....
|
||
<set name="kittens" ... >
|
||
<cache usage="read-write"/>
|
||
....
|
||
</set>
|
||
</class>]]></programlisting>
|
||
|
||
</sect2>
|
||
|
||
<sect2 id="performance-cache-nonstrict">
|
||
<title>厳密ではないread/write戦略</title>
|
||
|
||
<para>
|
||
アプリケーションがたまにしかデータを更新する必要はなく(すなわち二つのトランザクションが
|
||
同時に同じアイテムを更新しようとすることはほとんど起こらない)、厳密なトランザクション分離が
|
||
要求されないなら、 <literal>nonstrict-read-write</literal> キャッシュが適当かもしれません。
|
||
もしキャッシュがJTA環境で使われるなら、 <literal>hibernate.transaction.manager_lookup_class</literal>
|
||
を指定しなければなりません。他の環境では、 <literal>Session.close()</literal>
|
||
や <literal>Session.disconnect()</literal> が呼ばれたときに、確実にトランザクションが完了
|
||
していなければなりません。
|
||
</para>
|
||
|
||
</sect2>
|
||
|
||
<sect2 id="performance-cache-transactional">
|
||
<title>transactional戦略</title>
|
||
|
||
<para>
|
||
<literal>transactional</literal> キャッシュ戦略はJBoss TreeCacheのような完全なトランザクショナル
|
||
キャッシュプロバイダのサポートを提供します。
|
||
このようなキャッシュはJTA環境でのみ使用可能で、 <literal>hibernate.transaction.manager_lookup_class</literal>
|
||
を指定しなければなりません。
|
||
</para>
|
||
|
||
</sect2>
|
||
|
||
<para>
|
||
すべての同時並行性キャッシュ戦略をサポートしているキャッシュプロバイダはありません。
|
||
以下の表はどのプロバイダがどの同時並列性戦略に対応するかを表しています。
|
||
</para>
|
||
|
||
<table frame="topbot">
|
||
<title>同時並行性キャッシュ戦略のサポート</title>
|
||
<tgroup cols='5' align='left' colsep='1' rowsep='1'>
|
||
<colspec colname='c1' colwidth="1*"/>
|
||
<colspec colname='c2' colwidth="1*"/>
|
||
<colspec colname='c3' colwidth="1*"/>
|
||
<colspec colname='c4' colwidth="1*"/>
|
||
<colspec colname='c5' colwidth="1*"/>
|
||
<thead>
|
||
<row>
|
||
<entry>キャッシュ</entry>
|
||
<entry>read-only</entry>
|
||
<entry>厳密ではないread-write</entry>
|
||
<entry>read-write</entry>
|
||
<entry>transactional</entry>
|
||
</row>
|
||
</thead>
|
||
<tbody>
|
||
<row>
|
||
<entry>Hashtable(製品用として意図していません)</entry>
|
||
<entry>yes</entry>
|
||
<entry>yes</entry>
|
||
<entry>yes</entry>
|
||
<entry></entry>
|
||
</row>
|
||
<row>
|
||
<entry>EHCache</entry>
|
||
<entry>yes</entry>
|
||
<entry>yes</entry>
|
||
<entry>yes</entry>
|
||
<entry></entry>
|
||
</row>
|
||
<row>
|
||
<entry>OSCache</entry>
|
||
<entry>yes</entry>
|
||
<entry>yes</entry>
|
||
<entry>yes</entry>
|
||
<entry></entry>
|
||
</row>
|
||
<row>
|
||
<entry>SwarmCache</entry>
|
||
<entry>yes</entry>
|
||
<entry>yes</entry>
|
||
<entry></entry>
|
||
<entry></entry>
|
||
</row>
|
||
<row>
|
||
<entry>JBoss TreeCache</entry>
|
||
<entry>yes</entry>
|
||
<entry></entry>
|
||
<entry></entry>
|
||
<entry>yes</entry>
|
||
</row>
|
||
</tbody>
|
||
</tgroup>
|
||
</table>
|
||
|
||
</sect1>
|
||
|
||
<sect1 id="performance-sessioncache" revision="2">
|
||
<title>キャッシュの管理</title>
|
||
|
||
<para>
|
||
オブジェクトを <literal>save()</literal> 、 <literal>update()</literal> 、 <literal>saveOrUpdate()</literal>
|
||
に渡すとき、そして <literal>load()</literal> 、 <literal>get()</literal> 、 <literal>list()</literal> 、
|
||
<literal>iterate()</literal> 、 <literal>scroll()</literal> を使ってオブジェクトを復元するときには常に、
|
||
そのオブジェクトは <literal>Session</literal> の内部キャッシュに追加されます。
|
||
</para>
|
||
<para>
|
||
次に <literal>flush()</literal> が呼ばれると、オブジェクトの状態はデータベースと同期化されます。
|
||
もしこの同期が起こることを望まないときや、膨大な数のオブジェクトを処理していてメモリを効率的に
|
||
扱う必要があるときは、 <literal>evict()</literal> メソッドを使って一次キャッシュから
|
||
オブジェクトやコレクションを削除することが出来ます。
|
||
</para>
|
||
|
||
<programlisting><![CDATA[ScrollableResult cats = sess.createQuery("from Cat as cat").scroll(); //a huge result set
|
||
while ( cats.next() ) {
|
||
Cat cat = (Cat) cats.get(0);
|
||
doSomethingWithACat(cat);
|
||
sess.evict(cat);
|
||
}]]></programlisting>
|
||
|
||
<para>
|
||
<literal>Session</literal> はインスタンスがセッションキャッシュに含まれるかどうかを判断するための
|
||
<literal>contains()</literal> メソッドも提供します。
|
||
</para>
|
||
|
||
<para>
|
||
すべてのオブジェクトをセッションキャッシュから完全に取り除くには、<literal>Session.clear()</literal>
|
||
を呼び出してください。
|
||
</para>
|
||
|
||
<para>
|
||
二次キャッシュのために、 <literal>SessionFactory</literal> には
|
||
インスタンス、クラス全体、コレクションのインスタンス、コレクション全体をキャッシュから
|
||
削除するためのメソッドがそれぞれ定義されています。
|
||
</para>
|
||
|
||
<programlisting><![CDATA[sessionFactory.evict(Cat.class, catId); //evict a particular Cat
|
||
sessionFactory.evict(Cat.class); //evict all Cats
|
||
sessionFactory.evictCollection("Cat.kittens", catId); //evict a particular collection of kittens
|
||
sessionFactory.evictCollection("Cat.kittens"); //evict all kitten collections]]></programlisting>
|
||
|
||
<para>
|
||
<literal>CacheMode</literal> は特定のセッションが二次キャッシュとどのように相互作用するかを
|
||
指定します。
|
||
</para>
|
||
|
||
<itemizedlist>
|
||
<listitem>
|
||
<para>
|
||
<literal>CacheMode.NORMAL</literal> - アイテムの読み込みと書き込みで二次キャッシュを使います
|
||
</para>
|
||
</listitem>
|
||
<listitem>
|
||
<para>
|
||
<literal>CacheMode.GET</literal> - 読み込みは二次キャッシュから行いますが、データを
|
||
更新した場合を除いて二次キャッシュに書き込みをしません。
|
||
</para>
|
||
</listitem>
|
||
<listitem>
|
||
<para>
|
||
<literal>CacheMode.PUT</literal> - 二次キャッシュにアイテムを書き込みますが、読み込みには
|
||
二次キャッシュを使いません。
|
||
</para>
|
||
</listitem>
|
||
<listitem>
|
||
<para>
|
||
<literal>CacheMode.REFRESH</literal> - 二次キャッシュにアイテムを書き込みますが、読み込みには
|
||
二次キャッシュを使わず、 <literal>hibernate.cache.use_minimal_puts</literal>
|
||
の影響を受けずに、データベースから読み込むすべてのアイテムの二次キャッシュを強制的にリフレッシュします。
|
||
</para>
|
||
</listitem>
|
||
</itemizedlist>
|
||
|
||
<para>
|
||
二次キャッシュの内容やクエリキャッシュ領域を見るために、 <literal>Statistics</literal> APIを
|
||
使ってください。
|
||
</para>
|
||
|
||
<programlisting><![CDATA[Map cacheEntries = sessionFactory.getStatistics()
|
||
.getSecondLevelCacheStatistics(regionName)
|
||
.getEntries();]]></programlisting>
|
||
|
||
<para>
|
||
統計情報を有効にして、さらにオプションとして、キャッシュエントリを
|
||
人がより理解可能な形式で保持することをHibernateに強制します。
|
||
</para>
|
||
|
||
<programlisting><![CDATA[hibernate.generate_statistics true
|
||
hibernate.cache.use_structured_entries true]]></programlisting>
|
||
|
||
</sect1>
|
||
|
||
<sect1 id="performance-querycache" revision="1">
|
||
<title>クエリキャッシュ</title>
|
||
|
||
<para>
|
||
クエリのリザルトセットもキャッシュ出来ます。これは同じパラメータで何度も実行される
|
||
クエリに対してのみ有用です。クエリキャッシュを使うには、まず設定で有効にしなくてはなりません。
|
||
</para>
|
||
|
||
<programlisting><![CDATA[hibernate.cache.use_query_cache true]]></programlisting>
|
||
|
||
<para>
|
||
この設定は新たに二つのキャッシュ領域の作成を行います。一つはクエリのリザルトセットの
|
||
キャッシュ( <literal>org.hibernate.cache.StandardQueryCache</literal> )を保持し、
|
||
もう1つはクエリ可能なテーブルへの最新の更新タイムスタンプ
|
||
( <literal>org.hibernate.cache.UpdateTimestampsCache</literal> )を保持します。
|
||
クエリキャッシュはリザルトセットの実際の要素の状態はキャッシュしないことに
|
||
注意してください。キャッシュするのは識別子の値と、値型の結果のみです。
|
||
そのため、クエリキャッシュは常に二次キャッシュと一緒に使うべきです。
|
||
</para>
|
||
|
||
<para>
|
||
ほとんどのクエリはキャッシュの恩恵を受けないので、デフォルトではクエリはキャッシュされません。
|
||
キャッシュを有効にするには、 <literal>Query.setCacheable(true)</literal> を呼び出してください。
|
||
そうすればクエリが既存のキャッシュ結果を探し、クエリ実行時にその結果をキャッシュに追加する
|
||
ようになります。
|
||
</para>
|
||
|
||
<para>
|
||
クエリキャッシュの破棄ポリシーを細かく制御したいときは、 <literal>Query.setCacheRegion()</literal>
|
||
を呼び出して特定のクエリに対するキャッシュ領域を指定することが出来ます。
|
||
</para>
|
||
|
||
<programlisting><![CDATA[List blogs = sess.createQuery("from Blog blog where blog.blogger = :blogger")
|
||
.setEntity("blogger", blogger)
|
||
.setMaxResults(15)
|
||
.setCacheable(true)
|
||
.setCacheRegion("frontpages")
|
||
.list();]]></programlisting>
|
||
|
||
<para>
|
||
クエリが自身のクエリキャッシュ領域のリフレッシュを強制しなければならないなら、
|
||
<literal>Query.setCacheMode(CacheMode.REFRESH)</literal> を呼び出すべきです。これは
|
||
元となるデータが別のプロセスによって更新されたり(すなわちHibernateを通じて更新されない)、
|
||
アプリケーションに特定のクエリリザルトセットを選択してリフレッシュさせる場合に特に有用です。
|
||
さらに有用なもう一つの方法は、 <literal>SessionFactory.evictQueries()</literal>
|
||
によってクエリキャッシュ領域を消去することです。
|
||
</para>
|
||
|
||
</sect1>
|
||
☆14.1
|
||
<sect1 id="performance-collections">
|
||
<title>コレクションのパフォーマンスの理解</title>
|
||
|
||
<para>
|
||
コレクションの話題にはすでに多くの時間を使いました。この節では
|
||
コレクションが実行時にどのように振舞うかについての話題を2、3取り上げます。
|
||
</para>
|
||
|
||
<sect2 id="performance-collections-taxonomy">
|
||
<title>分類</title>
|
||
|
||
<para>Hibernateは3つの基本的なコレクションの種類を定義しています。
|
||
</para>
|
||
|
||
<itemizedlist>
|
||
<listitem>
|
||
<para>値のコレクション</para>
|
||
</listitem>
|
||
<listitem>
|
||
<para>一対多関連</para>
|
||
</listitem>
|
||
<listitem>
|
||
<para>多対多関連</para>
|
||
</listitem>
|
||
</itemizedlist>
|
||
|
||
<para>
|
||
この分類はさまざまなテーブルや外部キー関連を区別しますが、私たちが知る必要のある
|
||
関連モデルについてほとんどなにも教えてくれません。関連構造やパフォーマンスの特徴を
|
||
完全に理解するには、Hibernateがコレクションの行を更新、削除するために使う主キーの
|
||
構造もまた考えなければなりません。これは以下の分類を提示します。
|
||
</para>
|
||
|
||
<itemizedlist>
|
||
<listitem>
|
||
<para>インデックス付きコレクション</para>
|
||
</listitem>
|
||
<listitem>
|
||
<para>set</para>
|
||
</listitem>
|
||
<listitem>
|
||
<para>bag</para>
|
||
</listitem>
|
||
</itemizedlist>
|
||
|
||
<para>
|
||
すべてのインデックス付きコレクション(map、list、配列)は <literal><key></literal>
|
||
と <literal><index></literal> カラムからなる主キーを持っています。この場合は
|
||
コレクションの更新は非常に効率的です。主キーは有用なインデックスになり、Hibernateが
|
||
特定の行を更新または削除するときに、その行を効率的に見つけることができます。
|
||
</para>
|
||
|
||
<para>
|
||
setは <literal><key></literal> からなる主キーと要素のカラムを持っています。
|
||
これはコレクション要素のいくつかの型については効率的ではないかもしれません。
|
||
特に複合要素、大きなテキスト、バイナリフィールドでは非効率です。データベースは
|
||
複合主キーに効率的にインデックスを付けることができないからです。一方、1対多や多対多関連において、
|
||
特に人工識別子の場合は同じぐらい効率的です。(余談: <literal>SchemaExport</literal>
|
||
で実際に <literal><set></literal> の主キーを作りたいなら、すべてのカラムで
|
||
<literal>not-null="true"</literal> を宣言しなければなりません。)
|
||
</para>
|
||
|
||
<para>
|
||
<literal><idbag></literal> マッピングは代理キーを定義します。そのため
|
||
更新は常に非常に効率的です。事実上、これは最善のケースです。
|
||
</para>
|
||
|
||
<para>
|
||
bagは最悪のケースです。bagは要素の値の重複が可能で、インデックスカラムを持たないため、
|
||
主キーは定義されないかもしれません。Hibernateには重複した行を区別する方法がありません。
|
||
Hibernateはこの問題の解決のために、変更があったときには常に完全な削除
|
||
(一つの <literal>DELETE</literal> による)を行い、コレクションの再作成を行います。
|
||
これは非常に非効率的かもしれません。
|
||
</para>
|
||
|
||
<para>
|
||
1対多関連では、「主キー」はデータベースのテーブルの物理的な
|
||
主キーではないかもしれないことに注意してください。しかしこの場合でさえ、上記の分類はまだ有用です。
|
||
(Hibernateがコレクションの個々の行をどうやって「見つけるか」を表しています。)
|
||
</para>
|
||
|
||
</sect2>
|
||
|
||
<sect2 id="performance-collections-mostefficientupdate">
|
||
<title>更新にもっとも効率的なコレクション list、map、idbag、set</title>
|
||
|
||
<para>
|
||
上での議論から、インデックス付きコレクションと(普通の)setは要素の追加、削除、
|
||
更新でもっとも効率的な操作が出来ることは明らかです。
|
||
</para>
|
||
|
||
<para>
|
||
ほぼ間違いなく、多対多関連や値のコレクションにおいて、インデックス付きコレクションが
|
||
setよりも優れている点が一つ以上あります。 <literal>Set</literal> はその
|
||
構造のために、Hibernateは要素が「変更」されたときに行を決して <literal>UPDATE</literal>
|
||
しません。 <literal>Set</literal> への変更は常に(個々の行の)<literal>INSERT</literal>
|
||
と <literal>DELETE</literal> によって行います。繰り返しますが、これは一対多関連には
|
||
当てはまりません。
|
||
</para>
|
||
|
||
<para>
|
||
配列は遅延処理ができないという決まりなので、結論として、list、map、idbagがもっとも
|
||
パフォーマンスの良い(inverseではない)コレクションタイプとなります。setも
|
||
それほど違いはありません。Hibernateのアプリケーションでは、setはコレクションのもっとも
|
||
共通の種類として期待されます。setの表現は関連モデルではもっとも自然だからです。
|
||
</para>
|
||
|
||
<para>
|
||
しかし、よくデザインされたHibernateのドメインモデルでは、通常もっとも多いコレクションは
|
||
事実上 <literal>inverse="true"</literal> を指定した1対多関連です。これらの関連では、
|
||
更新は多対一の関連端で扱われ、コレクションの更新パフォーマンスの問題は当てはまりません。
|
||
</para>
|
||
|
||
</sect2>
|
||
|
||
<sect2 id="performance-collections-mostefficentinverse">
|
||
<title>inverseコレクションにもっとも最適なbagとlist</title>
|
||
|
||
<para>
|
||
bagを見放してしまう前に、bag(そしてlistも)がsetよりもずっとパフォーマンスが良い特別なケースを
|
||
紹介します。 <literal>inverse="true"</literal> のコレクション(一般的な1対多関連の使い方など)で、
|
||
bagの要素を初期化(フェッチ)する必要なくbagやlistに要素を追加できます!
|
||
これは <literal>Collection.add()</literal> や <literal>Collection.addAll()</literal>
|
||
はbagや <literal>List</literal> では常にtrueを返さなければならないからです
|
||
( <literal>Set</literal> とは異なります)。
|
||
これは以下の共通処理をより速くすることができます。
|
||
</para>
|
||
|
||
<programlisting><![CDATA[Parent p = (Parent) sess.load(Parent.class, id);
|
||
Child c = new Child();
|
||
c.setParent(p);
|
||
p.getChildren().add(c); //no need to fetch the collection!
|
||
sess.flush();]]></programlisting>
|
||
|
||
</sect2>
|
||
|
||
<sect2 id="performance-collections-oneshotdelete">
|
||
<title>一括削除</title>
|
||
|
||
<para>
|
||
時々、コレクションの要素を一つ一つ削除することは極めて非効率的になることがあります。
|
||
Hibernateは愚かではないので、新しい空のコレクションの場合( <literal>list.clear()</literal>
|
||
を呼び出した場合など)ではこれをすべきでないことを知っています。この場合は、Hibernateは
|
||
<literal>DELETE</literal> を一回発行して、それですべて終わります!
|
||
</para>
|
||
|
||
<para>
|
||
サイズ20のコレクションに一つの要素を追加し、それから二つの要素を削除するとします。
|
||
Hibernateは一つの <literal>INSERT</literal> 文と二つの <literal>DELETE</literal> 文を発行します
|
||
(コレクションがbagでなければ)。これは確かに望ましい動作です。
|
||
</para>
|
||
|
||
<para>
|
||
しかし、18個の要素を削除して2つを残し、それから3つ新しい要素を追加するとします。
|
||
このとき二つの方法があります。
|
||
</para>
|
||
|
||
<itemizedlist>
|
||
<listitem>
|
||
<para>
|
||
18行を一つ一つ削除して、3行を追加する
|
||
</para>
|
||
</listitem>
|
||
<listitem>
|
||
<para>
|
||
コレクション全体を削除( <literal>DELETE</literal> のSQLを一回)し、そして5つの要素すべてを
|
||
(一つずつ)追加する
|
||
</para>
|
||
</listitem>
|
||
</itemizedlist>
|
||
|
||
<para>
|
||
Hibernateはこの場合に2番目の方法がより速いだろうとわかるほど賢くはありません。
|
||
(そしてHibernateがこのように賢いことも望ましくないでしょう。このような振る舞いは
|
||
データベースのトリガなどを混乱させるかもしれません。)
|
||
</para>
|
||
|
||
<para>
|
||
幸いにも、元のコレクションを捨て(つまり参照をやめて)、現在の要素をすべて持つ新しいコレクションの
|
||
インスタンスを返すことで、いつでもこの振る舞い(2番目の戦略)を強制することが出来ます。
|
||
時にこれはとても便利で強力です。
|
||
</para>
|
||
|
||
<para>
|
||
もちろん、一括削除は <literal>inverse="true"</literal> を指定したコレクションには行いません。
|
||
</para>
|
||
|
||
</sect2>
|
||
|
||
</sect1>
|
||
|
||
<sect1 id="performance-monitoring" revision="1">
|
||
<title>パフォーマンスのモニタリング</title>
|
||
|
||
<para>
|
||
最適化はモニタリングやパフォーマンスを示す数値がなければ十分に行えません。
|
||
Hibernateは内部処理のすべての範囲の数値を提供します。
|
||
Hibernateの統計情報は <literal>SessionFactory</literal> 単位で取得可能です。
|
||
</para>
|
||
|
||
<sect2 id="performance-monitoring-sf" revision="2">
|
||
<title>SessionFactoryのモニタリング</title>
|
||
|
||
<para>
|
||
<literal>SessionFactory</literal> のメトリクスにアクセスするには2つの方法があります。
|
||
最初の方法は、 <literal>sessionFactory.getStatistics()</literal> を呼び出し、
|
||
自分で <literal>Statistics</literal> の読み込みや表示を行います。
|
||
</para>
|
||
|
||
<para>
|
||
<literal>StatisticsService</literal> MBeanを有効にしていれば、HibernateはJMXを使って
|
||
メトリクスを発行することもできます。1つのMBeanをすべての <literal>SessionFactory</literal>
|
||
に対して有効にするか、SessionFactoryごとに一つのMBeanを有効にすることが出来ます。
|
||
最小限の設定例である以下のコードを見てください。
|
||
</para>
|
||
|
||
<programlisting><![CDATA[// MBean service registration for a specific SessionFactory
|
||
Hashtable tb = new Hashtable();
|
||
tb.put("type", "statistics");
|
||
tb.put("sessionFactory", "myFinancialApp");
|
||
ObjectName on = new ObjectName("hibernate", tb); // MBean object name
|
||
|
||
StatisticsService stats = new StatisticsService(); // MBean implementation
|
||
stats.setSessionFactory(sessionFactory); // Bind the stats to a SessionFactory
|
||
server.registerMBean(stats, on); // Register the Mbean on the server]]></programlisting>
|
||
|
||
|
||
<programlisting><![CDATA[// MBean service registration for all SessionFactory's
|
||
Hashtable tb = new Hashtable();
|
||
tb.put("type", "statistics");
|
||
tb.put("sessionFactory", "all");
|
||
ObjectName on = new ObjectName("hibernate", tb); // MBean object name
|
||
|
||
StatisticsService stats = new StatisticsService(); // MBean implementation
|
||
server.registerMBean(stats, on); // Register the MBean on the server]]></programlisting>
|
||
|
||
<para>
|
||
TODO: これは意味がありません。最初のケースは、MBeanを直接復元して使用します。2番目のケースは、
|
||
使う前にsession factoryが持っているJNDI名を渡さなければなりません。
|
||
<literal>hibernateStatsBean.setSessionFactoryJNDIName("my/JNDI/Name")</literal> を使ってください。
|
||
</para>
|
||
<para>
|
||
<literal>SessionFactory</literal> に対してモニタリングの開始(終了)を行うことが出来ます。
|
||
</para>
|
||
<itemizedlist>
|
||
<listitem>
|
||
<para>
|
||
設定時には、 <literal>hibernate.generate_statistics</literal> を <literal>false</literal> にします
|
||
</para>
|
||
</listitem>
|
||
</itemizedlist>
|
||
<itemizedlist>
|
||
<listitem>
|
||
<para>
|
||
実行時に、 <literal>sf.getStatistics().setStatisticsEnabled(true)</literal> または
|
||
<literal>hibernateStatsBean.setStatisticsEnabled(true)</literal> を呼び出します
|
||
</para>
|
||
</listitem>
|
||
</itemizedlist>
|
||
|
||
<para>
|
||
統計は <literal>clear()</literal> メソッドを使って手動でリセットすることが出来ます。
|
||
サマリは <literal>logSummary()</literal> メソッドを使ってloggerに送ることが出来ます
|
||
(infoレベルです)。
|
||
</para>
|
||
|
||
</sect2>
|
||
|
||
<sect2 id="performance-monitoring-metrics" revision="1">
|
||
<title>メトリクス</title>
|
||
|
||
<para>
|
||
多くのものがあります。すべての使用可能なカウンタは <literal>Statistics</literal>
|
||
インターフェイスのAPIに書かれており、3つの分類があります。
|
||
</para>
|
||
<itemizedlist>
|
||
<listitem>
|
||
<para>
|
||
メトリクスは一般的な <literal>Session</literal> の使い方と関係しています。
|
||
オープンしたセッションの数がJDBCコネクションと関連しているのと同じです。
|
||
</para>
|
||
</listitem>
|
||
<listitem>
|
||
<para>
|
||
メトリクスは要素、コレクション、クエリやキャッシュなど全体に
|
||
関係しています(別名はグローバルメトリクスです)。
|
||
</para>
|
||
</listitem>
|
||
<listitem>
|
||
<para>
|
||
メトリクスの詳細は特定のエンティティ、コレクション、クエリ、キャッシュ領域に関係しています。
|
||
</para>
|
||
</listitem>
|
||
</itemizedlist>
|
||
|
||
<para>
|
||
例として、キャッシュのヒット、ヒットミスや、要素、コレクション、クエリの割合、クエリの実行に
|
||
必要な平均時間を確認できます。ミリ秒の数値はJavaの近似を受けることに注意してください。
|
||
HibernateはJVMの精度に制限され、プラットフォームによっては10秒単位でしか正確でないかもしれません。
|
||
</para>
|
||
|
||
<para>
|
||
単純なgetterはグローバルメトリクス(すなわち特定のエンティティ、コレクション、キャッシュ領域などに縛られない)
|
||
にアクセスするために使います。特定のエンティティ、コレクション、キャッシュ領域のメトリクスは、
|
||
それらの名前や、クエリのHQL、SQL表現によってアクセスすることが出来ます。さらに詳しい情報は、
|
||
<literal>Statistics</literal> 、 <literal>EntityStatistics</literal> 、 <literal>CollectionStatistics</literal>
|
||
、 <literal>SecondLevelCacheStatistics</literal> 、 <literal>QueryStatistics</literal> APIのjavadocを
|
||
参照してください。以下のコードは簡単な例です。
|
||
</para>
|
||
|
||
<programlisting><![CDATA[Statistics stats = HibernateUtil.sessionFactory.getStatistics();
|
||
|
||
double queryCacheHitCount = stats.getQueryCacheHitCount();
|
||
double queryCacheMissCount = stats.getQueryCacheMissCount();
|
||
double queryCacheHitRatio =
|
||
queryCacheHitCount / (queryCacheHitCount + queryCacheMissCount);
|
||
|
||
log.info("Query Hit ratio:" + queryCacheHitRatio);
|
||
|
||
EntityStatistics entityStats =
|
||
stats.getEntityStatistics( Cat.class.getName() );
|
||
long changes =
|
||
entityStats.getInsertCount()
|
||
+ entityStats.getUpdateCount()
|
||
+ entityStats.getDeleteCount();
|
||
log.info(Cat.class.getName() + " changed " + changes + "times" );]]></programlisting>
|
||
|
||
<para>
|
||
すべてのエンティティ、コレクション、クエリ、キャッシュ領域に対して行う場合は、
|
||
<literal>getQueries()</literal> 、 <literal>getEntityNames()</literal>、
|
||
<literal>getCollectionRoleNames()</literal> 、 <literal>getSecondLevelCacheRegionNames()</literal> メソッドで
|
||
それぞれの名前のリストを取得することが出来ます。
|
||
</para>
|
||
|
||
</sect2>
|
||
|
||
</sect1>
|
||
|
||
</chapter> |