パフォーマンスの改善 フェッチ戦略 フェッチ戦略 は、アプリケーションが関連をナビゲートする必要がある ときに、Hibernateが関連オブジェクトを復元するために使用する戦略です。フェッチ戦略はO/Rマッピングの メタデータに宣言するか、特定のHQL、 Criteria クエリでオーバーライドします。 Hibernate3は次に示すフェッチ戦略を定義しています。 結合フェッチ - Hibernateは OUTER JOIN を使って、 関連するインスタンスやコレクションを1つの SELECT で復元します。 セレクトフェッチ - 2回目の SELECT で関連するエンティティやコレクションを復元します。 lazy="false" で明示的に遅延フェッチを無効にしなければ、この2回目のselectは実際に 関連にアクセスしたときのみ実行されるでしょう。 サブセレクトフェッチ - 2回目の SELECT で、直前のクエリやフェッチで復元したすべての要素に関連するコレクションを 復元します。 lazy="false" で明示的に遅延フェッチを無効にしなければ、この2回目のselectは実際に 関連にアクセスしたときのみ実行されるでしょう。 バッチフェッチ - セレクトフェッチのための最適化された戦略 - Hibernateはエンティティのインスタンスやコレクションの一群を1回の SELECT で復元します。これは主キーや外部キーのリストを指定することに により行います。 Hibernateは次に示す戦略とも区別をします。 即時フェッチ - 所有者のオブジェクトがロードされたときに、 関連、コレクションは即時にフェッチされます。 遅延コレクションフェッチ - アプリケーションがコレクションに 対して操作を行ったときにコレクションをフェッチします。 (これはコレクションに対するデフォルトの動作です) "特別な遅延"コレクションフェッチ - コレクションの要素 1つ1つが独立して、必要なときにデータベースから取得されます。 Hibernateは必要ないならば、コレクション全体をメモリにフェッチすることは 避けます(とても大きなコレクションに適しています)。 プロキシフェッチ - 単一値関連は、識別子のgetter以外の メソッドが関連オブジェクトで呼び出されるときにフェッチされます。 "プロキシなし"フェッチ - 単一値関連は、インスタンス変数に アクセスされたときにフェッチされます。プロキシフェッチと比較すると、この方法は 遅延の度合いが少ない(関連は識別子にアクセスしただけでもフェッチされます) ですが、より透過的で、アプリケーションにプロキシが存在しないように見せます。 この方法はビルド時のバイトコード組み込みが必要になり、使う場面はまれです。 遅延属性フェッチ - 属性や単一値関連は、インスタンス変数にアクセスした ときにフェッチされます。この方法はビルド時のバイトコード組み込みが必要になり、 使う場面はまれです。 二つの直行する概念があります: いつ 関連をフェッチするか、 そして、 どうやって フェッチするか(どんなSQLを使って)。 これらを混同しないでください! fetch はパフォーマンスチューニングに使います。 lazy はあるクラスの分離されたインスタンスのうち、どのデータを常に 使用可能にするかの取り決めを定義します。 遅延関連の働き デフォルトでは、Hibernate3はコレクションに対しては遅延セレクトフェッチを使い、 単一値関連には遅延プロキシフェッチを使います。これらのデフォルト動作はほぼすべての アプリケーションのほぼすべての関連で意味があります。 注: hibernate.default_batch_fetch_size をセットしたときは、Hibernateは 遅延フェッチのためのバッチフェッチ最適化を使うでしょう (この最適化はより細かいレベルで有効にすることも出来ます)。 しかし、遅延フェッチは知っておかなければならない一つの問題があります。 Hibernateのsessionをオープンしているコンテキストの外から遅延関連にアクセスすると、 例外が発生します。例: Session がクローズされたとき、permissionsコレクションは 初期化されていないため、このコレクションは自身の状態をロードできません。 Hibernateは切り離されたオブジェクトの遅延初期化はサポート していません 。修正方法として、コレクションから読み込みを行うコードを トランザクションをコミットする直前に移動させます。 一方で、 lazy="false" を関連マッピングに指定することで、 遅延処理をしないコレクションや関連を使うことが出来ます。 しかしながら、遅延初期化はほぼすべてのコレクションや関連で使われることを 意図しています。もしあなたのオブジェクトモデルの中に遅延処理をしない関連を 多く定義してしまうと、Hibernateは最終的にはトランザクション毎に ほぼ完全なデータベースをメモリの中にフェッチすることになるでしょう! 他方では、特定のトランザクションにおいてセレクトフェッチの代わりに 結合フェッチ(当然これは遅延処理ではなくなります)を選択したいことが 時々あります。これからフェッチ戦略をカスタマイズする方法をお見せします。 Hibernate3では、フェッチ戦略を選択する仕組みは単一値関連とコレクションで 変わりはありません。 フェッチ戦略のチューニング セレクトフェッチ(デフォルト)はN+1セレクト問題という大きな弱点があるため、 マッピング定義で結合フェッチを有効にすることができます。 ]]> マッピング定義で定義した フェッチ 戦略は次のものに影響します。 get()load() による復元 関連にナビゲートしたときに発生する暗黙的な復元 Criteria クエリ サブセレクト フェッチを使うHQLクエリ たとえどんなフェッチ戦略を使ったとしても、遅延ではないグラフはメモリに読み込まれることが 保証されます。つまり、特定のHQLクエリを実行するためにいくつかのSELECT文が即時実行される ことがあるので注意してください。 通常は、マッピング定義でフェッチのカスタマイズは行いません。 代わりに、デフォルトの動作のままにしておいて、HQLで left join fetch を 指定することで特定のトランザクションで動作をオーバーライドします。 これはHibernateに初回のセレクトで外部結合を使って関連を先にフェッチするように指定しています。 Criteria クエリのAPIでは、 setFetchMode(FetchMode.JOIN) を使うことが出来ます。 もし get()load() で使われる フェッチ戦略を変えたいと感じたときには、単純に Criteria クエリを使ってください。例: (これはいくつかのORMソリューションが"fetch plan"と呼んでいるものと同じです。) N+1セレクト問題を避けるためのまったく違う方法は、第2レベルキャッシュを使うことです。 単一端関連プロキシ コレクションの遅延フェッチは、Hibernate自身の実装による永続コレクションを使って 実現しています。しかし、単一端関連における遅延処理では、違う仕組みが 必要です。対象の関連エンティティはプロキシでなければなりません。Hibernateは (すばらしいCGLIBライブラリによる)実行時のバイトコード拡張を 使って永続オブジェクトの遅延初期化プロキシを実現しています。 デフォルトでは、Hibernate3は(開始時に)すべての永続クラスのプロキシを生成し、 それらを使って、 many-to-oneone-to-one 関連の 遅延フェッチを可能にしています。 マッピングファイルで proxy 属性によって、クラスのプロキシインターフェイスとして 使うインターフェイスを宣言できます。デフォルトでは、Hibernateはそのクラスのサブクラスを使います。 プロキシクラスは少なくともパッケージ可視でデフォルトコンストラクタを実装しなければ ならないことに注意してください。すべての永続クラスにこのコンストラクタを推奨します! ポリモーフィズムのクラスに対してこの方法を適用するときにいくつか考慮することがあります。 例: ...... ..... ]]> 第一に、 Cat のインスタンスは DomesticCat にキャストできません。たとえ基となるインスタンスが DomesticCat であったとしてもです。 第二に、プロキシの == は成立しないことがあります。 しかし、これは見かけほど悪い状況というわけではありません。たとえ異なったプロキシオブジェクトへの 二つの参照があったとしても、基となるインスタンスは同じオブジェクトです。 第三に、 final クラスや final メソッドを持つクラスに CGLIBプロキシを使えません。 最後に、もし永続オブジェクトのインスタンス化時(例えば、初期化処理やデフォルトコンストラクタの中で) になんらかのリソースが必要となるなら、そのリソースもまたプロキシを通して取得されます。 実際には、プロキシクラスは永続クラスのサブクラスです。 これらの問題はJavaの単一継承モデルの原理上の制限のためです。もしこれらの問題を避けたいのなら、 ビジネスメソッドを宣言したインターフェイスをそれぞれ永続クラスで実装しなければなりません。 マッピングファイルでこれらのインターフェイスを指定する必要があります。例: ...... ..... ]]> CatImplCat インターフェイスを実装するのに対し、 DomesticCatImplDomesticCat を実装します。 すると、 load()iterate() は、 CatDomesticCat のインスタンスのプロキシを 返します。( list() は通常はプロキシを返さないことに注意してください。) 関連も遅延初期化されます。これはプロパティを Cat 型で宣言しなければ ならないことを意味します。 CatImpl ではありません。 プロキシの初期化を 必要としない 操作も存在します。 equals() (永続クラスが equals() を オーバーライドしないとき) hashCode() (永続クラスが hashCode() を オーバーライドしないとき) 識別子のgetterメソッド Hibernateは equals()hashCode() をオーバーライドした 永続クラスを検出します。 デフォルトの lazy="proxy" の代わりに、 lazy="no-proxy" を 選んだことで、型変換に関連する問題を回避することが出来ます。 しかし、ビルド時のバイトコード組み込みが必要になり、どのような操作であっても、 ただちにプロキシの初期化を行うことになるでしょう。 コレクションとプロキシの初期化 LazyInitializationException は、 Session のスコープ外から 初期化していないコレクションやプロキシにアクセスされたときに、Hibernateによってスローされます。 すなわち、コレクションやプロキシへの参照を持つエンティティが分離された状態の時です。 Session をクローズする前にプロキシやコレクションの初期化を確実に 行いたいときがあります。もちろん、 cat.getSex()cat.getKittens().size() などを常に呼び出すことで初期化を強制することはできます。 しかしこれはコードを読む人を混乱させ、汎用的なコードという点からも不便です。 staticメソッドの Hibernate.initialize()Hibernate.isInitialized() は遅延初期化のコレクションやプロキシを扱うときに便利な方法をアプリケーションに提供します。 Hibernate.initialize(cat) は、 Session がオープンしている限りは cat プロキシを強制的に初期化します。 Hibernate.initialize( cat.getKittens() ) はkittensコレクションに対して同様の 効果があります。 別の選択肢として、必要なすべてのコレクションやプロキシがロードされるまで Session をオープンにしておく方法があります。いくつかのアプリケーションの アーキテクチャでは、特にHibernateによるデータアクセスを行うコードと、それを使う コードが異なるアプリケーションのレイヤーや、物理的に異なるプロセッサのときには、 コレクションが初期化されるときに Session がオープンしていることを 保証する問題があります。この問題に対しては2つの基本的な方法があります。 Webベースのアプリケーションでは、 ビューのレンダリングが完了し、リクエストが終わる一番最後で Session をクローズするために、サーブレットフィルタを使うことができます( Open Session in View パターンです)。もちろん、アプリケーション基盤の例外処理の正確性が非常に重要になります。 ビューのレンダリング中に例外が発生したときでさえ、ユーザに処理が戻る前に Session のクローズとトランザクションの終了を行う ことが不可欠になります。 HibernateのWikiに載っている"Open Session in View"パターンの例を参照してください。 ビジネス層が分離しているアプリケーションでは、ビジネスロジックは Web層で必要になるすべてのコレクションを事前に"準備"する必要があります。 これは特定のユースケースで必要となるプレゼンテーション/Web層に対し、 ビジネス層がすべてのデータをロードし、すべてのデータを初期化して返すべきと いうことを意味しています。通常は、アプリケーションはWeb層で必要なコレクション それぞれに対して Hibernate.initialize() を呼び出すか (この呼び出しはセッションをクローズする前に行う必要があります)、 Hibernateクエリの FETCH 節や CriteriaFetchMode.JOIN を使ってコレクションを先に復元します。 普通は Session Facade パターンの代わりに Command パターンを採用するほうがより簡単です。 初期化されていないコレクション(もしくは他のプロキシ)にアクセスする前に、 merge()lock() を使って新しい Session に以前にロードされたオブジェクトを追加することも出来ます。 アドホックなトランザクションのセマンティクスを導入したので、Hibernateは これを自動的に行わず、 行うべきでもありません 大きなコレクションを初期化したくはないが、コレクションについてのなんらかの情報(サイズのような) やデータのサブセットを必要とすることがあります。 コレクションフィルタを使うことで、初期化せずにコレクションのサイズを取得することが出来ます。 createFilter() メソッドは、コレクション全体を初期化する必要なしに、コレクションの サブセットを復元するために効果的に使えます。 バッチフェッチの使用 Hibernateはバッチフェッチを効率的に使用できます。一つのプロキシ(もしくはコレクション)がアクセス されると、Hibernateはいくつかの初期化していないプロキシをロードすることができます。バッチフェッチは 遅延セレクトフェッチ戦略に対する最適化です。バッチフェッチの調整には2つの方法があります。 クラスレベルとコレクションレベルです。 クラス、要素のバッチフェッチは理解が簡単です。実行時の次の場面を想像してください。 Session にロードされた25個の Cat インスタンスが存在し、 それぞれの Catowner である Person への関連を持ちます。 Person クラスは lazy="true" のプロキシでマッピングされています。 もし今すべてのCatに対して繰り返し getOwner() を呼び出すと、Hibernateは デフォルトでは25回の SELECT を実行し、ownerプロキシの復元をします。 この振る舞いを Person のマッピングの batch-size の指定で調整できます。 ...]]> Hibernateはクエリを3回だけを実行するようになります。パターンは10, 10, 5です。 コレクションのバッチフェッチも有効にすることが出来ます。例として、それぞれの PersonCat の遅延コレクションを持っており、 10個のPersonが Sesssion にロードされたとすると、すべてのPersonに 対して繰り返し getCats() を呼び出すことで、計10回の SELECT が発生します。もし Person のマッピングで cats コレクションのバッチフェッチを有効にすれば、Hibernateはコレクションの事前フェッチが出来ます。 ... ]]> batch-size が3なので、Hibernateは4回の SELECT で3個、3個、3個、1個をロードします。繰り返すと、属性の値は特定の Session の中の初期化されていないコレクションの期待数に依存します。 コレクションのバッチフェッチはアイテムのネストしたツリー、 すなわち、代表的な部品表のパターンが ある場合に特に有用です。(しかし、読み込みが多いツリーでは ネストしたset具体化したパス がよりよい選択になります。) サブセレクトフェッチの使用 一つの遅延コレクションや単一値プロキシがフェッチされなければいけないとき、Hibernateは それらすべてをロードし、サブセレクトのオリジナルクエリが再度実行されます。これは バッチフェッチと同じ方法で動き、少しずつのロードは行いません。 遅延プロパティフェッチの使用 Hibernate3はプロパティごとの遅延フェッチをサポートしています。この最適化手法は グループのフェッチ としても知られています。これはほとんど 要望から出た機能であることに注意してください。実際には列読み込みの最適化よりも、 行読み込みの最適化が非常に重要です。 しかし、クラスのいくつかのプロパティだけを読み込むことは、既存のテーブルが何百もの列を持ち、 データモデルを改善できないなどの極端な場合には有用です。 遅延プロパティ読み込みを有効にするには、対象のプロパティのマッピングで lazy 属性をセットしてください。 ]]> 遅延プロパティ読み込みはビルド時のバイトコード組み込みを必要とします!もし 永続クラスに組み込みがされていないなら、Hibernateは黙って遅延プロパティの設定を無視して、 即時フェッチに戻します。 バイトコード組み込みは以下のAntタスクを使ってください。 ]]> 不要な列を読み込まないための、別の(よりよい?)方法は、少なくとも 読み込みのみのトランザクションにおいては、HQLやCriteriaクエリの射影 機能を使うことです。この方法はビルド時のバイトコード組み込みが不要になり、 より良い解決方法です。 HQLで fetch all properties を使うことで、普通どおりの プロパティの即時フェッチングを強制することが出来ます。 第2レベルキャッシュ Hibernateの Session は永続データのトランザクションレベルのキャッシュです。 class-by-classとcollection-by-collectionごとの、クラスタレベルやJVMレベル ( SessionFactory レベル)のキャッシュを設定することが出来ます。 クラスタ化されたキャッシュにつなぐことさえ出来ます。しかし注意してください。 キャッシュは他のアプリケーションによる永続層の変更を 考慮しません(キャッシュデータを定期的に期限切れにする設定は出来ます)。 Hibernateが使用するキャッシュ実装は、hibernate.cache.provider_class プロパティに org.hibernate.cache.CacheProvider を実装したクラス名を指定することで変更できます。 Hibernateは多くのオープンソースのキャッシュプロバイダをビルトイン実装で持っています(後にリストがあります)。 加えて、前に説明したように、あなた自身が独自の実装をして、それを組み込むことも出来ます。 バージョン3.2より前ではEhCacheがデフォルトのキャッシュプロバイダであることに注意してください。 バージョン3.2ではこれは当てはまりません。 キャッシュプロバイダ キャッシュ プロバイダクラス タイプ クラスタセーフ クエリキャッシュのサポート Hashtable(製品用として意図していません) org.hibernate.cache.HashtableCacheProvider メモリ yes EHCache org.hibernate.cache.EhCacheProvider メモリ、ディスク yes OSCache org.hibernate.cache.OSCacheProvider メモリ、ディスク yes SwarmCache org.hibernate.cache.SwarmCacheProvider クラスタ(ipマルチキャスト) yes(クラスタ無効化) JBoss TreeCache org.hibernate.cache.TreeCacheProvider クラスタ(ipマルチキャスト)、トランザクショナル yes(複製) yes(時刻同期が必要)
キャッシュのマッピング クラスやコレクションのマッピングの <cache> 要素は以下の形式です。 ]]> usage (required) specifies the caching strategy: transactional, read-write, nonstrict-read-write or read-only region (optional, defaults to the class or collection role name) specifies the name of the second level cache region include (optional, defaults to all) non-lazy specifies that properties of the entity mapped with lazy="true" may not be cached when attribute-level lazy fetching is enabled または(よりよい方法として?)、 hibernate.cfg.xml<class-cache><collection-cache> 要素を指定することも出来ます。 usage 属性は キャッシュの並列性戦略 を指定します。 read only戦略 もしアプリケーションが読み込みのみ必要で、永続クラスのインスタンスを変更しないなら、 read-only キャッシュを使うことが出来ます。これはもっとも単純で もっともパフォーマンスの良い戦略です。クラスタでの使用も完全に安全です。 .... ]]> read/write戦略 アプリケーションがデータを更新する必要があるなら、 read-write キャッシュが適当かも しれません。このキャッシュ戦略は、シリアライザブルなトランザクション分離レベルが要求されるなら、 決して使うべきではありません。もしキャッシュがJTA環境で使われるなら、JTA TransactionManager を取得するための方法を示す hibernate.transaction.manager_lookup_class プロパティを指定しなければなりません。他の環境では、 Session.close()Session.disconnect() が呼ばれたときに、確実にトランザクションが完了 していなければなりません。 もしクラスタでこの戦略を使いたいなら、基となるキャッシュの実装がロックをサポート していることを保証しなければなりません。 組み込みのキャッシュプロバイダは サポートしていません .... .... ]]> 厳密ではないread/write戦略 アプリケーションがたまにしかデータを更新する必要はなく(すなわち二つのトランザクションが 同時に同じアイテムを更新しようとすることはほとんど起こらない)、厳密なトランザクション分離が 要求されないなら、 nonstrict-read-write キャッシュが適当かもしれません。 もしキャッシュがJTA環境で使われるなら、 hibernate.transaction.manager_lookup_class を指定しなければなりません。他の環境では、 Session.close()Session.disconnect() が呼ばれたときに、確実にトランザクションが完了 していなければなりません。 transactional戦略 transactional キャッシュ戦略はJBoss TreeCacheのような完全なトランザクショナル キャッシュプロバイダのサポートを提供します。 このようなキャッシュはJTA環境でのみ使用可能で、 hibernate.transaction.manager_lookup_class を指定しなければなりません。 すべての同時並行性キャッシュ戦略をサポートしているキャッシュプロバイダはありません。 以下の表はどのプロバイダがどの同時並列性戦略に対応するかを表しています。 同時並行性キャッシュ戦略のサポート キャッシュ read-only 厳密ではないread-write read-write transactional Hashtable(製品用として意図していません) yes yes yes EHCache yes yes yes OSCache yes yes yes SwarmCache yes yes JBoss TreeCache yes yes
キャッシュの管理 オブジェクトを save()update()saveOrUpdate() に渡すとき、そして load()get()list()iterate()scroll() を使ってオブジェクトを復元するときには常に、 そのオブジェクトは Session の内部キャッシュに追加されます。 次に flush() が呼ばれると、オブジェクトの状態はデータベースと同期化されます。 もしこの同期が起こることを望まないときや、膨大な数のオブジェクトを処理していてメモリを効率的に 扱う必要があるときは、 evict() メソッドを使って一次キャッシュから オブジェクトやコレクションを削除することが出来ます。 Session はインスタンスがセッションキャッシュに含まれるかどうかを判断するための contains() メソッドも提供します。 すべてのオブジェクトをセッションキャッシュから完全に取り除くには、Session.clear() を呼び出してください。 二次キャッシュのために、 SessionFactory には インスタンス、クラス全体、コレクションのインスタンス、コレクション全体をキャッシュから 削除するためのメソッドがそれぞれ定義されています。 CacheMode は特定のセッションが二次キャッシュとどのように相互作用するかを 指定します。 CacheMode.NORMAL - アイテムの読み込みと書き込みで二次キャッシュを使います CacheMode.GET - 読み込みは二次キャッシュから行いますが、データを 更新した場合を除いて二次キャッシュに書き込みをしません。 CacheMode.PUT - 二次キャッシュにアイテムを書き込みますが、読み込みには 二次キャッシュを使いません。 CacheMode.REFRESH - 二次キャッシュにアイテムを書き込みますが、読み込みには 二次キャッシュを使わず、 hibernate.cache.use_minimal_puts の影響を受けずに、データベースから読み込むすべてのアイテムの二次キャッシュを強制的にリフレッシュします。 二次キャッシュの内容やクエリキャッシュ領域を見るために、 Statistics APIを 使ってください。 統計情報を有効にして、さらにオプションとして、キャッシュエントリを 人がより理解可能な形式で保持することをHibernateに強制します。 クエリキャッシュ クエリのリザルトセットもキャッシュ出来ます。これは同じパラメータで何度も実行される クエリに対してのみ有用です。クエリキャッシュを使うには、まず設定で有効にしなくてはなりません。 この設定は新たに二つのキャッシュ領域の作成を行います。一つはクエリのリザルトセットの キャッシュ( org.hibernate.cache.StandardQueryCache )を保持し、 もう1つはクエリ可能なテーブルへの最新の更新タイムスタンプ ( org.hibernate.cache.UpdateTimestampsCache )を保持します。 クエリキャッシュはリザルトセットの実際の要素の状態はキャッシュしないことに 注意してください。キャッシュするのは識別子の値と、値型の結果のみです。 そのため、クエリキャッシュは常に二次キャッシュと一緒に使うべきです。 ほとんどのクエリはキャッシュの恩恵を受けないので、デフォルトではクエリはキャッシュされません。 キャッシュを有効にするには、 Query.setCacheable(true) を呼び出してください。 そうすればクエリが既存のキャッシュ結果を探し、クエリ実行時にその結果をキャッシュに追加する ようになります。 クエリキャッシュの破棄ポリシーを細かく制御したいときは、 Query.setCacheRegion() を呼び出して特定のクエリに対するキャッシュ領域を指定することが出来ます。 クエリが自身のクエリキャッシュ領域のリフレッシュを強制しなければならないなら、 Query.setCacheMode(CacheMode.REFRESH) を呼び出すべきです。これは 元となるデータが別のプロセスによって更新されたり(すなわちHibernateを通じて更新されない)、 アプリケーションに特定のクエリリザルトセットを選択してリフレッシュさせる場合に特に有用です。 さらに有用なもう一つの方法は、 SessionFactory.evictQueries() によってクエリキャッシュ領域を消去することです。 ☆14.1 コレクションのパフォーマンスの理解 コレクションの話題にはすでに多くの時間を使いました。この節では コレクションが実行時にどのように振舞うかについての話題を2、3取り上げます。 分類 Hibernateは3つの基本的なコレクションの種類を定義しています。 値のコレクション 一対多関連 多対多関連 この分類はさまざまなテーブルや外部キー関連を区別しますが、私たちが知る必要のある 関連モデルについてほとんどなにも教えてくれません。関連構造やパフォーマンスの特徴を 完全に理解するには、Hibernateがコレクションの行を更新、削除するために使う主キーの 構造もまた考えなければなりません。これは以下の分類を提示します。 インデックス付きコレクション set bag すべてのインデックス付きコレクション(map、list、配列)は <key><index> カラムからなる主キーを持っています。この場合は コレクションの更新は非常に効率的です。主キーは有用なインデックスになり、Hibernateが 特定の行を更新または削除するときに、その行を効率的に見つけることができます。 setは <key> からなる主キーと要素のカラムを持っています。 これはコレクション要素のいくつかの型については効率的ではないかもしれません。 特に複合要素、大きなテキスト、バイナリフィールドでは非効率です。データベースは 複合主キーに効率的にインデックスを付けることができないからです。一方、1対多や多対多関連において、 特に人工識別子の場合は同じぐらい効率的です。(余談: SchemaExport で実際に <set> の主キーを作りたいなら、すべてのカラムで not-null="true" を宣言しなければなりません。) <idbag> マッピングは代理キーを定義します。そのため 更新は常に非常に効率的です。事実上、これは最善のケースです。 bagは最悪のケースです。bagは要素の値の重複が可能で、インデックスカラムを持たないため、 主キーは定義されないかもしれません。Hibernateには重複した行を区別する方法がありません。 Hibernateはこの問題の解決のために、変更があったときには常に完全な削除 (一つの DELETE による)を行い、コレクションの再作成を行います。 これは非常に非効率的かもしれません。 1対多関連では、「主キー」はデータベースのテーブルの物理的な 主キーではないかもしれないことに注意してください。しかしこの場合でさえ、上記の分類はまだ有用です。 (Hibernateがコレクションの個々の行をどうやって「見つけるか」を表しています。) 更新にもっとも効率的なコレクション list、map、idbag、set 上での議論から、インデックス付きコレクションと(普通の)setは要素の追加、削除、 更新でもっとも効率的な操作が出来ることは明らかです。 ほぼ間違いなく、多対多関連や値のコレクションにおいて、インデックス付きコレクションが setよりも優れている点が一つ以上あります。 Set はその 構造のために、Hibernateは要素が「変更」されたときに行を決して UPDATE しません。 Set への変更は常に(個々の行の)INSERTDELETE によって行います。繰り返しますが、これは一対多関連には 当てはまりません。 配列は遅延処理ができないという決まりなので、結論として、list、map、idbagがもっとも パフォーマンスの良い(inverseではない)コレクションタイプとなります。setも それほど違いはありません。Hibernateのアプリケーションでは、setはコレクションのもっとも 共通の種類として期待されます。setの表現は関連モデルではもっとも自然だからです。 しかし、よくデザインされたHibernateのドメインモデルでは、通常もっとも多いコレクションは 事実上 inverse="true" を指定した1対多関連です。これらの関連では、 更新は多対一の関連端で扱われ、コレクションの更新パフォーマンスの問題は当てはまりません。 inverseコレクションにもっとも最適なbagとlist bagを見放してしまう前に、bag(そしてlistも)がsetよりもずっとパフォーマンスが良い特別なケースを 紹介します。 inverse="true" のコレクション(一般的な1対多関連の使い方など)で、 bagの要素を初期化(フェッチ)する必要なくbagやlistに要素を追加できます! これは Collection.add()Collection.addAll() はbagや List では常にtrueを返さなければならないからです ( Set とは異なります)。 これは以下の共通処理をより速くすることができます。 一括削除 時々、コレクションの要素を一つ一つ削除することは極めて非効率的になることがあります。 Hibernateは愚かではないので、新しい空のコレクションの場合( list.clear() を呼び出した場合など)ではこれをすべきでないことを知っています。この場合は、Hibernateは DELETE を一回発行して、それですべて終わります! サイズ20のコレクションに一つの要素を追加し、それから二つの要素を削除するとします。 Hibernateは一つの INSERT 文と二つの DELETE 文を発行します (コレクションがbagでなければ)。これは確かに望ましい動作です。 しかし、18個の要素を削除して2つを残し、それから3つ新しい要素を追加するとします。 このとき二つの方法があります。 18行を一つ一つ削除して、3行を追加する コレクション全体を削除( DELETE のSQLを一回)し、そして5つの要素すべてを (一つずつ)追加する Hibernateはこの場合に2番目の方法がより速いだろうとわかるほど賢くはありません。 (そしてHibernateがこのように賢いことも望ましくないでしょう。このような振る舞いは データベースのトリガなどを混乱させるかもしれません。) 幸いにも、元のコレクションを捨て(つまり参照をやめて)、現在の要素をすべて持つ新しいコレクションの インスタンスを返すことで、いつでもこの振る舞い(2番目の戦略)を強制することが出来ます。 時にこれはとても便利で強力です。 もちろん、一括削除は inverse="true" を指定したコレクションには行いません。 パフォーマンスのモニタリング 最適化はモニタリングやパフォーマンスを示す数値がなければ十分に行えません。 Hibernateは内部処理のすべての範囲の数値を提供します。 Hibernateの統計情報は SessionFactory 単位で取得可能です。 SessionFactoryのモニタリング SessionFactory のメトリクスにアクセスするには2つの方法があります。 最初の方法は、 sessionFactory.getStatistics() を呼び出し、 自分で Statistics の読み込みや表示を行います。 StatisticsService MBeanを有効にしていれば、HibernateはJMXを使って メトリクスを発行することもできます。1つのMBeanをすべての SessionFactory に対して有効にするか、SessionFactoryごとに一つのMBeanを有効にすることが出来ます。 最小限の設定例である以下のコードを見てください。 TODO: これは意味がありません。最初のケースは、MBeanを直接復元して使用します。2番目のケースは、 使う前にsession factoryが持っているJNDI名を渡さなければなりません。 hibernateStatsBean.setSessionFactoryJNDIName("my/JNDI/Name") を使ってください。 SessionFactory に対してモニタリングの開始(終了)を行うことが出来ます。 設定時には、 hibernate.generate_statisticsfalse にします 実行時に、 sf.getStatistics().setStatisticsEnabled(true) または hibernateStatsBean.setStatisticsEnabled(true) を呼び出します 統計は clear() メソッドを使って手動でリセットすることが出来ます。 サマリは logSummary() メソッドを使ってloggerに送ることが出来ます (infoレベルです)。 メトリクス 多くのものがあります。すべての使用可能なカウンタは Statistics インターフェイスのAPIに書かれており、3つの分類があります。 メトリクスは一般的な Session の使い方と関係しています。 オープンしたセッションの数がJDBCコネクションと関連しているのと同じです。 メトリクスは要素、コレクション、クエリやキャッシュなど全体に 関係しています(別名はグローバルメトリクスです)。 メトリクスの詳細は特定のエンティティ、コレクション、クエリ、キャッシュ領域に関係しています。 例として、キャッシュのヒット、ヒットミスや、要素、コレクション、クエリの割合、クエリの実行に 必要な平均時間を確認できます。ミリ秒の数値はJavaの近似を受けることに注意してください。 HibernateはJVMの精度に制限され、プラットフォームによっては10秒単位でしか正確でないかもしれません。 単純なgetterはグローバルメトリクス(すなわち特定のエンティティ、コレクション、キャッシュ領域などに縛られない) にアクセスするために使います。特定のエンティティ、コレクション、キャッシュ領域のメトリクスは、 それらの名前や、クエリのHQL、SQL表現によってアクセスすることが出来ます。さらに詳しい情報は、 StatisticsEntityStatisticsCollectionStatisticsSecondLevelCacheStatisticsQueryStatistics APIのjavadocを 参照してください。以下のコードは簡単な例です。 すべてのエンティティ、コレクション、クエリ、キャッシュ領域に対して行う場合は、 getQueries()getEntityNames()getCollectionRoleNames()getSecondLevelCacheRegionNames() メソッドで それぞれの名前のリストを取得することが出来ます。