Hibernate.orgCommunity Documentation
フェッチ戦略 は、アプリケーションが関連をナビゲートする必要があるときに、 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 をオープンしているコンテキストの外から遅延関連にアクセスすると、例外が発生します。例:
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!
Session
がクローズされたとき、 permissions コレクションは初期化されていないため、このコレクションは自身の状態をロードできません。 Hibernate は切り離されたオブジェクトの遅延初期化はサポートしていません 。修正方法として、コレクションから読み込みを行うコードをトランザクションをコミットする直前に移動させます。
一方で、 lazy="false"
を関連マッピングに指定することで、遅延処理をしないコレクションや関連を使うことが出来ます。しかしながら、遅延初期化はほぼすべてのコレクションや関連で使われることを意図しています。もしあなたのオブジェクトモデルの中に遅延処理をしない関連を多く定義してしまうと、 Hibernate は最終的にはトランザクション毎にほぼ完全なデータベースをメモリの中にフェッチすることになるでしょう。
他方では、特定のトランザクションにおいてセレクトフェッチの代わりに結合フェッチ(当然これは遅延処理ではなくなります)を選択したいことが時々あります。これからフェッチ戦略をカスタマイズする方法をお見せします。 Hibernate3 では、フェッチ戦略を選択する仕組みは単一値関連とコレクションで変わりはありません。
セレクトフェッチ(デフォルト)は N+1 セレクト問題という大きな弱点があるため、マッピング定義で結合フェッチを有効にすることができます:
<set name="permissions"
fetch="join">
<key column="userId"/>
<one-to-many class="Permission"/>
</set
<many-to-one name="mother" class="Cat" fetch="join"/>
マッピング定義で定義した フェッチ
戦略は次のものに影響します:
get()
や load()
による復元
関連にナビゲートしたときに発生する暗黙的な復元
Criteria
クエリ
サブセレクト
フェッチを使う HQL クエリ
たとえどんなフェッチ戦略を使ったとしても、遅延ではないグラフはメモリに読み込まれることが保証されます。つまり、特定の HQL クエリを実行するためにいくつかの SELECT 文が即時実行されることがあるので注意してください。
通常は、マッピング定義でフェッチのカスタマイズは行いません。代わりに、デフォルトの動作のままにしておいて、 HQL で left join fetch
を指定することで特定のトランザクションで動作をオーバーライドします。これは Hibernate に初回のセレクトで外部結合を使って関連を先にフェッチするように指定しています。 Criteria
クエリの API では、 setFetchMode(FetchMode.JOIN)
を使うことが出来ます。
もし get()
や load()
で使われるフェッチ戦略を変えたいと感じたときには、単純に Criteria
クエリを使ってください。例:
User user = (User) session.createCriteria(User.class)
.setFetchMode("permissions", FetchMode.JOIN)
.add( Restrictions.idEq(userId) )
.uniqueResult();
(これはいくつかの ORM ソリューションが "fetch plan" と呼んでいるものと同じです。)
N+1 セレクト問題を避けるためのまったく違う方法は、第2レベルキャッシュを使うことです。
コレクションの遅延フェッチは、 Hibernate 自身の実装による永続コレクションを使って実現しています。しかし、単一端関連における遅延処理では、違う仕組みが必要です。対象の関連エンティティはプロキシでなければなりません。 Hibernate は(すばらしい CGLIB ライブラリによる)実行時のバイトコード拡張を使って永続オブジェクトの遅延初期化プロキシを実現しています。
デフォルトでは、 Hibernate3 は(開始時に)すべての永続クラスのプロキシを生成し、それらを使って、 many-to-one
や one-to-one
関連の遅延フェッチを可能にしています。
マッピングファイルで proxy
属性によって、クラスのプロキシインターフェースとして使うインターフェースを宣言できます。デフォルトでは、 Hibernate はそのクラスのサブクラスを使います。 プロキシクラスは少なくともパッケージ可視でデフォルトコンストラクタを実装しなければならないことに注意してください。すべての永続クラスにこのコンストラクタを推奨します。
ポリモーフィズムのクラスに対してこの方法を適用するときにいくつか考慮することがあります。例:
<class name="Cat" proxy="Cat">
......
<subclass name="DomesticCat">
.....
</subclass>
</class>
第一に、 Cat
のインスタンスは DomesticCat
にキャストできません。たとえ基となるインスタンスが DomesticCat
であったとしてもです:
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!
....
}
第二に、プロキシの ==
は成立しないことがあります。
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
しかし、これは見かけほど悪い状況というわけではありません。たとえ異なったプロキシオブジェクトへの二つの参照があったとしても、基となるインスタンスは同じオブジェクトです:
cat.setWeight(11.0); // hit the db to initialize the proxy
System.out.println( dc.getWeight() ); // 11.0
第三に、 final
クラスや final
メソッドを持つクラスに CGLIB プロキシを使えません。
最後に、もし永続オブジェクトのインスタンス化時 (例えば、初期化処理やデフォルトコンストラクタの中で) になんらかのリソースが必要となるなら、そのリソースもまたプロキシを通して取得されます。実際には、プロキシクラスは永続クラスのサブクラスです。
これらの問題は Java の単一継承モデルの原理上の制限のためです。もしこれらの問題を避けたいのなら、ビジネスメソッドを宣言したインターフェースをそれぞれ永続クラスで実装しなければなりません。マッピングファイルでこれらのインターフェースを指定する必要があります。例:
<class name="CatImpl" proxy="Cat">
......
<subclass name="DomesticCatImpl" proxy="DomesticCat">
.....
</subclass>
</class>
Then proxies for instances of Cat
and DomesticCat
can be returned by load()
or iterate()
.
Cat cat = (Cat) session.load(CatImpl.class, catid);
Iterator iter = session.createQuery("from CatImpl as cat where cat.name='fritz'").iterate();
Cat fritz = (Cat) iter.next();
list()
does not usually return proxies.
関連も遅延初期化されます。これはプロパティを 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
節や Criteria
の FetchMode.JOIN
を使ってコレクションを先に復元します。普通は Session Facade パターンの代わりに Command パターンを採用するほうがより簡単です。
初期化されていないコレクション(もしくは他のプロキシ)にアクセスする前に、 merge()
や lock()
を使って新しい Session
に以前にロードされたオブジェクトを追加することも出来ます。アドホックなトランザクションのセマンティクスを導入したので、 Hibernate はこれを自動的に行わず、 行うべきでもありません 。
大きなコレクションを初期化したくはないが、コレクションについてのなんらかの情報(サイズのような)やデータのサブセットを必要とすることがあります。
コレクションフィルタを使うことで、初期化せずにコレクションのサイズを取得することが出来ます:
( (Integer) s.createFilter( collection, "select count(*)" ).list().get(0) ).intValue()
createFilter()
メソッドは、コレクション全体を初期化する必要なしに、コレクションのサブセットを復元するために効果的に使えます:
s.createFilter( lazyCollection, "").setFirstResult(0).setMaxResults(10).list();
Hibernate はバッチフェッチを効率的に使用できます。一つのプロキシ(もしくはコレクション)がアクセスされると、 Hibernate はいくつかの初期化していないプロキシをロードすることができます。バッチフェッチは遅延セレクトフェッチ戦略に対する最適化です。バッチフェッチの調整には2つの方法があります。クラスレベルとコレクションレベルです。
クラス、要素のバッチフェッチは理解が簡単です。実行時の次の場面を想像してください。 Session
にロードされた25個の Cat
インスタンスが存在し、それぞれの Cat
は owner
である Person
への関連を持ちます。 Person
クラスは lazy="true"
のプロキシでマッピングされています。もし今すべての Cat に対して繰り返し getOwner()
を呼び出すと、 Hibernate はデフォルトでは25回の SELECT
を実し、 owner プロキシの復元をします。この振る舞いを Person
のマッピングの batch-size
の指定で調整できます。
<class name="Person" batch-size="10">...</class>
Hibernate はクエリを3回だけを実行するようになります。パターンは 10, 10, 5 です。
コレクションのバッチフェッチも有効にすることが出来ます。例として、それぞれの Person
が Cat
の遅延コレクションを持っており、 10 個の Person が Sesssion
にロードされたとすると、すべての Person に対して繰り返し getCats()
を呼び出すことで、計10回の SELECT
が発生します。もし Person
のマッピングで cats
コレクションのバッチフェッチを有効にすれば、 Hibernate はコレクションの事前フェッチが出来ます。
<class name="Person">
<set name="cats" batch-size="3">
...
</set>
</class>
batch-size
が 3 なので、 Hibernate は 4 回の SELECT
で 3 個、 3 個、 3 個、 1 個をロードします。繰り返すと、属性の値は特定の Session
の中の初期化されていないコレクションの期待数に依存します。
コレクションのバッチフェッチはアイテムのネストしたツリー、すなわち、代表的な部品表のパターンがある場合に特に有用です。(しかし、読み込みが多いツリーでは ネストした set や 具体化したパス がよりよい選択になります。)
一つの遅延コレクションや単一値プロキシがフェッチされなければいけないとき、 Hibernate はそれらすべてをロードし、サブセレクトのオリジナルクエリが再度実行されます。これはバッチフェッチと同じ方法で動き、少しずつのロードは行いません。
Another way to affect the fetching strategy for loading associated objects is through something called a fetch profile, which is a named configuration associated with the org.hibernate.SessionFactory
but enabled, by name, on the org.hibernate.Session
. Once enabled on a org.hibernate.Session
, the fetch profile will be in affect for that org.hibernate.Session
until it is explicitly disabled.
So what does that mean? Well lets explain that by way of an example which show the different available approaches to configure a fetch profile:
例21.1 Specifying a fetch profile using @FetchProfile
@Entity
@FetchProfile(name = "customer-with-orders", fetchOverrides = {
@FetchProfile.FetchOverride(entity = Customer.class, association = "orders", mode = FetchMode.JOIN)
})
public class Customer {
@Id
@GeneratedValue
private long id;
private String name;
private long customerNumber;
@OneToMany
private Set<Order> orders;
// standard getter/setter
...
}
例21.2 Specifying a fetch profile using <fetch-profile>
outside <class>
node
<hibernate-mapping>
<class name="Customer">
...
<set name="orders" inverse="true">
<key column="cust_id"/>
<one-to-many class="Order"/>
</set>
</class>
<class name="Order">
...
</class>
<fetch-profile name="customer-with-orders">
<fetch entity="Customer" association="orders" style="join"/>
</fetch-profile>
</hibernate-mapping>
例21.3 Specifying a fetch profile using <fetch-profile>
inside <class>
node
<hibernate-mapping>
<class name="Customer">
...
<set name="orders" inverse="true">
<key column="cust_id"/>
<one-to-many class="Order"/>
</set>
<fetch-profile name="customer-with-orders">
<fetch association="orders" style="join"/>
</fetch-profile>
</class>
<class name="Order">
...
</class>
</hibernate-mapping>
Now normally when you get a reference to a particular customer, that customer's set of orders will be lazy meaning we will not yet have loaded those orders from the database. Normally this is a good thing. Now lets say that you have a certain use case where it is more efficient to load the customer and their orders together. One way certainly is to use "dynamic fetching" strategies via an HQL or criteria queries. But another option is to use a fetch profile to achieve that. The following code will load both the customer andtheir orders:
例21.4 Activating a fetch profile for a given Session
Session session = ...;
session.enableFetchProfile( "customer-with-orders" ); // name matches from mapping
Customer customer = (Customer) session.get( Customer.class, customerId );
@FetchProfile
definitions are global and it does not matter on which class you place them. You can place the @FetchProfile
annotation either onto a class or package (package-info.java). In order to define multiple fetch profiles for the same class or package @FetchProfiles
can be used.
Currently only join style fetch profiles are supported, but they plan is to support additional styles. See HHH-3414 for details.
Hibernate3 はプロパティごとの遅延フェッチをサポートしています。この最適化手法は グループのフェッチ としても知られています。これはほとんど要望から出た機能であることに注意してください。実際には列読み込みの最適化よりも、行読み込みの最適化が非常に重要です。しかし、クラスのいくつかのプロパティだけを読み込むことは、既存のテーブルが何百もの列を持ち、データモデルを改善できないなどの極端な場合には有用です。
遅延プロパティ読み込みを有効にするには、対象のプロパティのマッピングで lazy
属性をセットしてください:
<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>
遅延プロパティ読み込みはビルド時のバイトコード組み込みを必要とします。もし永続クラスに組み込みがされていないなら、 Hibernate は黙って遅延プロパティの設定を無視して、即時フェッチに戻します。
バイトコード組み込みは以下の Ant タスクを使ってください:
<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>
不要な列を読み込まないための、別の(よりよい?)方法は、少なくとも読み込みのみのトランザクションにおいては、 HQL や Criteria クエリの射影機能を使うことです。この方法はビルド時のバイトコード組み込みが不要になり、より良い解決方法です。
HQL で fetch all properties
を使うことで、普通どおりのプロパティの即時フェッチングを強制することが出来ます。
Hibernate の Session
は永続データのトランザクションレベルのキャッシュです。 class-by-class と collection-by-collection ごとの、クラスタレベルや JVM レベル ( SessionFactory
レベル)のキャッシュを設定することが出来ます。クラスタ化されたキャッシュにつなぐことさえ出来ます。しかし注意してください。キャッシュは他のアプリケーションによる永続層の変更を考慮しません(キャッシュデータを定期的に期限切れにする設定は出来ます)。
You have the option to tell Hibernate which caching implementation to use by specifying the name of a class that implements org.hibernate.cache.CacheProvider
using the property hibernate.cache.provider_class
. Hibernate is bundled with a number of built-in integrations with the open-source cache providers that are listed in 表21.1「キャッシュプロバイダ」. You can also implement your own and plug it in as outlined above. Note that versions prior to Hibernate 3.2 use EhCache as the default cache provider.
表21.1 キャッシュプロバイダ
キャッシュ | プロバイダクラス | タイプ | クラスタセーフ | クエリキャッシュのサポート |
---|---|---|---|---|
Hashtable(製品用として意図していません) | org.hibernate.cache.HashtableCacheProvider | メモリ | yes | |
EHCache | org.hibernate.cache.EhCacheProvider | memory, disk, transactional, clustered | yes | yes |
OSCache | org.hibernate.cache.OSCacheProvider | メモリ、ディスク | yes | |
SwarmCache | org.hibernate.cache.SwarmCacheProvider | クラスタ(ip マルチキャスト) | yes(クラスタ無効化) | |
JBoss Cache 1.x | org.hibernate.cache.TreeCacheProvider | クラスタ(ip マルチキャスト)、トランザクショナル | yes(複製) | yes(時刻同期が必要) |
JBoss Cache 2 | org.hibernate.cache.jbc.JBossCacheRegionFactory | クラスタ(ip マルチキャスト)、トランザクショナル | yes (replication or invalidation) | yes(時刻同期が必要) |
As we have done in previous chapters we are looking at the two different possibiltites to configure caching. First configuration via annotations and then via Hibernate mapping files.
By default, entities are not part of the second level cache and we recommend you to stick to this setting. However, you can override this by setting the shared-cache-mode
element in your persistence.xml
file or by using the javax.persistence.sharedCache.mode
property in your configuration. The following values are possible:
ENABLE_SELECTIVE
(Default and recommended value): entities are not cached unless explicitly marked as cacheable.
DISABLE_SELECTIVE
: entities are cached unless explicitly marked as not cacheable.
ALL
: all entities are always cached even if marked as non cacheable.
NONE
: no entity are cached even if marked as cacheable. This option can make sense to disable second-level cache altogether.
The cache concurrency strategy used by default can be set globaly via the hibernate.cache.default_cache_concurrency_strategy
configuration property. The values for this property are:
read-only
read-write
nonstrict-read-write
transactional
It is recommended to define the cache concurrency strategy per entity rather than using a global one. Use the @org.hibernate.annotations.Cache
annotation for that.
例21.5 Definition of cache concurrency strategy via @Cache
@Entity
@Cacheable
@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public class Forest { ... }
Hibernate also let's you cache the content of a collection or the identifiers if the collection contains other entities. Use the @Cache
annotation on the collection property.
例21.6 Caching collections using annotations
@OneToMany(cascade=CascadeType.ALL, fetch=FetchType.EAGER)
@JoinColumn(name="CUST_ID")
@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public SortedSet<Ticket> getTickets() {
return tickets;
}
例21.7「@Cache annotation with attributes」shows the @org.hibernate.annotations.Cache
annotations with its attributes. It allows you to define the caching strategy and region of a given second level cache.
例21.7 @Cache
annotation with attributes
@Cache( CacheConcurrencyStrategy usage(); String region() default ""; String include() default "all"; )
usage: the given cache concurrency strategy (NONE, READ_ONLY, NONSTRICT_READ_WRITE, READ_WRITE, TRANSACTIONAL) | |
region (optional): the cache region (default to the fqcn of the class or the fq role name of the collection) | |
|
Let's now take a look at Hibernate mapping files. There the <cache>
element of a class or collection mapping is used to configure the second level cache. Looking at 例21.8「The Hibernate <cache> mapping element」 the parallels to anotations is obvious.
例21.8 The Hibernate <cache>
mapping element
<cache usage="transactional|read-write|nonstrict-read-write|read-only" region="RegionName" include="all|non-lazy" />
| |
| |
|
Alternatively to <cache>
, you can use <class-cache>
and <collection-cache>
elements in hibernate.cfg.xml
.
Let's now have a closer look at the different usage strategies
もしアプリケーションが読み込みのみ必要で、永続クラスのインスタンスを変更しないなら、 read-only
キャッシュを使うことが出来ます。これはもっとも単純でもっともパフォーマンスの良い戦略です。クラスタでの使用も完全に安全です。
アプリケーションがデータを更新する必要があるなら、 read-write
キャッシュが適当かもしれません。このキャッシュ戦略は、シリアライザブルなトランザクション分離レベルが要求されるなら、決して使うべきではありません。もしキャッシュが JTA 環境で使われるなら、 JTA TransactionManager
を取得するための方法を示す hibernate.transaction.manager_lookup_class
プロパティを指定しなければなりません。他の環境では、 Session.close()
や Session.disconnect()
が呼ばれたときに、確実にトランザクションが完了していなければなりません。もしクラスタでこの戦略を使いたいなら、基となるキャッシュの実装がロックをサポートしていることを保証しなければなりません。組み込みのキャッシュプロバイダは サポートしていません 。
アプリケーションがたまにしかデータを更新する必要はなく(すなわち二つのトランザクションが同時に同じアイテムを更新しようとすることはほとんど起こらない)、厳密なトランザクション分離が要求されないなら、 nonstrict-read-write
キャッシュが適当かもしれません。もしキャッシュが JTA 環境で使われるなら、 hibernate.transaction.manager_lookup_class
を指定しなければなりません。他の環境では、 Session.close()
や Session.disconnect()
が呼ばれたときに、確実にトランザクションが完了していなければなりません。
transactional
キャッシュ戦略は JBoss TreeCache のような完全なトランザクショナルキャッシュプロバイダのサポートを提供します。このようなキャッシュは JTA 環境でのみ使用可能で、 hibernate.transaction.manager_lookup_class
を指定しなければなりません。
オブジェクトを save()
、 update()
、 saveOrUpdate()
に渡すとき、そして load()
、 get()
、 list()
、 iterate()
、 scroll()
を使ってオブジェクトを復元するときには常に、そのオブジェクトは Session
の内部キャッシュに追加されます。
次に flush()
が呼ばれると、オブジェクトの状態はデータベースと同期化されます。もしこの同期が起こることを望まないときや、膨大な数のオブジェクトを処理していてメモリを効率的に扱う必要があるときは、 evict()
メソッドを使って一次キャッシュからオブジェクトやコレクションを削除することが出来ます。
例21.9 Explcitly evicting a cached instance from the first level cache using Session.evict()
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);
}
Session
はインスタンスがセッションキャッシュに含まれるかどうかを判断するための contains()
メソッドも提供します。
すべてのオブジェクトをセッションキャッシュから完全に取り除くには、 Session.clear()
を呼び出してください。
二次キャッシュのために、 SessionFactory
にはインスタンス、クラス全体、コレクションのインスタンス、コレクション全体をキャッシュから削除するためのメソッドがそれぞれ定義されています。
例21.10 Second-level cache eviction via SessionFactoty.evict()
and SessionFacyory.evictCollection()
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
CacheMode
は特定のセッションが二次キャッシュとどのように相互作用するかを指定します。
CacheMode.NORMAL
- アイテムの読み込みと書き込みで二次キャッシュを使います
CacheMode.GET
- 読み込みは二次キャッシュから行いますが、データを更新した場合を除いて二次キャッシュに書き込みをしません。
CacheMode.PUT
- 二次キャッシュにアイテムを書き込みますが、読み込みには二次キャッシュを使いません。
CacheMode.REFRESH
- 二次キャッシュにアイテムを書き込みますが、読み込みには二次キャッシュを使わず、 hibernate.cache.use_minimal_puts
の影響を受けずに、データベースから読み込むすべてのアイテムの二次キャッシュを強制的にリフレッシュします。
二次キャッシュの内容やクエリキャッシュ領域を見るために、 Statistics
API を使ってください:
例21.11 Browsing the second-level cache entries via the Statistics
API
Map cacheEntries = sessionFactory.getStatistics()
.getSecondLevelCacheStatistics(regionName)
.getEntries();
統計情報を有効にして、さらにオプションとして、キャッシュエントリを人がより理解可能な形式で保持することを Hibernate に強制します:
例21.12 Enabling Hibernate statistics
hibernate.generate_statistics true hibernate.cache.use_structured_entries true
Query result sets can also be cached. This is only useful for queries that are run frequently with the same parameters.
Caching of query results introduces some overhead in terms of your applications normal transactional processing. For example, if you cache results of a query against Person Hibernate will need to keep track of when those results should be invalidated because changes have been committed against Person. That, coupled with the fact that most applications simply gain no benefit from caching query results, leads Hibernate to disable caching of query results by default. To use query caching, you will first need to enable the query cache:
hibernate.cache.use_query_cache true
This setting creates two new cache regions:
org.hibernate.cache.StandardQueryCache
, holding the cached query results
org.hibernate.cache.UpdateTimestampsCache
, holding timestamps of the most recent updates to queryable tables. These are used to validate the results as they are served from the query cache.
If you configure your underlying cache implementation to use expiry or timeouts is very important that the cache timeout of the underlying cache region for the UpdateTimestampsCache be set to a higher value than the timeouts of any of the query caches. In fact, we recommend that the the UpdateTimestampsCache region not be configured for expiry at all. Note, in particular, that an LRU cache expiry policy is never appropriate.
As mentioned above, most queries do not benefit from caching or their results. So by default, individual queries are not cached even after enabling query caching. To enable results caching for a particular query, call org.hibernate.Query.setCacheable(true)
. This call allows the query to look for existing cache results or add its results to the cache when it is executed.
The query cache does not cache the state of the actual entities in the cache; it caches only identifier values and results of value type. For this reaso, the query cache should always be used in conjunction with the second-level cache for those entities expected to be cached as part of a query result cache (just as with collection caching).
クエリキャッシュの破棄ポリシーを細かく制御したいときは、 Query.setCacheRegion()
を呼び出して特定のクエリに対するキャッシュ領域を指定することが出来ます。
List blogs = sess.createQuery("from Blog blog where blog.blogger = :blogger")
.setEntity("blogger", blogger)
.setMaxResults(15)
.setCacheable(true)
.setCacheRegion("frontpages")
.list();
If you want to force the query cache to refresh one of its regions (disregard any cached results it finds there) you can use org.hibernate.Query.setCacheMode(CacheMode.REFRESH)
. In conjunction with the region you have defined for the given query, Hibernate will selectively force the results cached in that particular region to be refreshed. This is particularly useful in cases where underlying data may have been updated via a separate process and is a far more efficient alternative to bulk eviction of the region via org.hibernate.SessionFactory.evictQueries()
.
In the previous sections we have covered collections and their applications. In this section we explore some more issues in relation to collections at runtime.
Hibernate は3つの基本的なコレクションの種類を定義しています:
値のコレクション
一対多関連
多対多関連
この分類はさまざまなテーブルや外部キー関連を区別しますが、私たちが知る必要のある関連モデルについてほとんどなにも教えてくれません。関連構造やパフォーマンスの特徴を完全に理解するには、 Hibernate がコレクションの行を更新、削除するために使う主キーの構造もまた考えなければなりません。これは以下の分類を提示します。
インデックス付きコレクション
set
bag
すべてのインデックス付きコレクション (マップ、リスト、配列) は <key>
と <index>
カラムからなる主キーを持っています。この場合はコレクションの更新は非常に効率的です。主キーは有用なインデックスになり、 Hibernate が特定の行を更新または削除するときに、その行を効率的に見つけることができます。
set は <key>
からなる主キーと要素のカラムを持っています。これはコレクション要素のいくつかの型については効率的ではないかもしれません。特に複合要素、大きなテキスト、バイナリフィールドでは非効率です。データベースは複合主キーに効率的にインデックスを付けることができないからです。一方、一対多や多対多関連において、特に人工識別子の場合は同じぐらい効率的です。(余談: SchemaExport
で実際に <set>
の主キーを作りたいなら、すべてのカラムで not-null="true"
を宣言しなければなりません。)
<idbag>
マッピングは代理キーを定義します。そのため更新は常に非常に効率的です。事実上、これは最善のケースです。
bag は最悪のケースです。 bag は要素の値の重複が可能で、インデックスカラムを持たないため、主キーは定義されないかもしれません。 Hibernate には重複した行を区別する方法がありません。 Hibernate はこの問題の解決のために、変更があったときには常に完全な削除(一つの DELETE
による)を行い、コレクションの再作成を行います。これは非常に非効率的かもしれません。
一対多関連では、「主キー」はデータベースのテーブルの物理的な主キーではないかもしれないことに注意してください。しかしこの場合でさえ、上記の分類はまだ有用です。(Hibernateがコレクションの個々の行をどうやって「見つけるか」を表しています。)
上での議論から、インデックス付きコレクションと(普通の) set は要素の追加、削除、更新でもっとも効率的な操作が出来ることは明らかです。
ほぼ間違いなく、多対多関連や値のコレクションにおいて、インデックス付きコレクションが set よりも優れている点が一つ以上あります。 Set
はその構造のために、 Hibernate は要素が「変更」されたときに行を決して UPDATE
しません。 Set
への変更は常に(個々の行の) INSERT
と DELETE
によって行います。繰り返しますが、これは一対多関連には当てはまりません。
配列は遅延処理ができないという決まりなので、結論として、list、map、idbag がもっともパフォーマンスの良い(inverse ではない)コレクションタイプとなります。 set もそれほど違いはありません。 Hibernate のアプリケーションでは、 set はコレクションのもっとも共通の種類として期待されます。 "set" の表現は関連モデルではもっとも自然だからです。
しかし、よくデザインされた Hibernate のドメインモデルでは、通常もっとも多いコレクションは事実上 inverse="true"
を指定した一対多関連です。これらの関連では、更新は多対一の関連端で扱われ、コレクションの更新パフォーマンスの問題は当てはまりません。
bag を見放してしまう前に、 bag (そして list も)が set よりもずっとパフォーマンスが良い特別なケースを紹介します。 inverse="true"
のコレクション(一般的な一対多関連の使い方など)で、 bag の要素を初期化(フェッチ)する必要なく bag や list に要素を追加できます。これは Collection.add()
や Collection.addAll()
は bag や List
では常に true を返さなければならないからです ( Set
とは異なります)。これは以下の共通処理をより速くすることができます。
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();
時々、コレクションの要素を一つ一つ削除することは極めて非効率的になることがあります。 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
のメトリクスにアクセスするには2つの方法があります。最初の方法は、 sessionFactory.getStatistics()
を呼び出し、自分で Statistics
の読み込みや表示を行います。
StatisticsService
MBean を有効にしていれば、 Hibernate は JMX を使ってメトリクスを発行することもできます。1つの MBean をすべての SessionFactory
に対して有効にするか、 SessionFactory ごとに一つの MBean を有効にすることが出来ます。最小限の設定例である以下のコードを見てください:
// 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
// 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
SessionFactory
に対してモニタリングの開始(終了)を行うことが出来ます。
設定時には、 hibernate.generate_statistics
を false
にします
実行時に、 sf.getStatistics().setStatisticsEnabled(true)
または hibernateStatsBean.setStatisticsEnabled(true)
を呼び出します
統計は clear()
メソッドを使って手動でリセットすることが出来ます。サマリは logSummary()
メソッドを使って logger に送ることが出来ます(info レベルです)。
多くのものがあります。すべての使用可能なカウンタは Statistics
インターフェースの API に書かれており、3つの分類があります:
メトリクスは一般的な Session
の使い方と関係しています。オープンしたセッションの数が JDBC コネクションと関連しているのと同じです。
メトリクスは要素、コレクション、クエリやキャッシュなど全体に関係しています(別名はグローバルメトリクスです)。
メトリクスの詳細は特定のエンティティ、コレクション、クエリ、キャッシュ領域に関係しています。
例として、キャッシュのヒット、ヒットミスや、要素、コレクション、クエリの割合、クエリの実行に必要な平均時間を確認できます。ミリ秒の数値は Java の近似を受けることに注意してください。 Hibernate は JVM の精度に制限され、プラットフォームによっては10秒単位でしか正確でないかもしれません。
単純な getter はグローバルメトリクス(すなわち特定のエンティティ、コレクション、キャッシュ領域などに縛られない)にアクセスするために使います。特定のエンティティ、コレクション、キャッシュ領域のメトリクスは、それらの名前や、クエリの HQL 、 SQL 表現によってアクセスすることが出来ます。さらに詳しい情報は、 Statistics
、 EntityStatistics
、 CollectionStatistics
、 SecondLevelCacheStatistics
、 QueryStatistics
API の javadoc を参照してください。以下のコードは簡単な例です:
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" );
すべてのエンティティ、コレクション、クエリ、キャッシュ領域に対して行う場合は、 getQueries()
、 getEntityNames()
、 getCollectionRoleNames()
、 getSecondLevelCacheRegionNames()
メソッドでそれぞれの名前のリストを取得することが出来ます。
製作著作 © 2004 Red Hat, Inc.