Hibernate.orgCommunity Documentation

第20章 パフォーマンスの改善

20.1. フェッチ戦略
20.1.1. 遅延関連の働き
20.1.2. フェッチ戦略のチューニング
20.1.3. 単一端関連プロキシ
20.1.4. コレクションとプロキシの初期化
20.1.5. バッチフェッチの使用
20.1.6. サブセレクトフェッチの使用
20.1.7. Fetch profiles
20.1.8. 遅延プロパティフェッチの使用
20.2. 第2レベルキャッシュ
20.2.1. キャッシュのマッピング
20.2.2. read only 戦略
20.2.3. read/write 戦略
20.2.4. 厳密ではない read/write 戦略
20.2.5. transactional 戦略
20.2.6. Cache-provider/concurrency-strategy compatibility
20.3. キャッシュの管理
20.4. クエリキャッシュ
20.4.1. Enabling query caching
20.4.2. Query cache regions
20.5. コレクションのパフォーマンスの理解
20.5.1. 分類
20.5.2. 更新にもっとも効率的なコレクション list、map、idbag、set
20.5.3. inverse コレクションにもっとも最適な bag と list
20.5.4. 一括削除
20.6. パフォーマンスのモニタリング
20.6.1. SessionFactory のモニタリング
20.6.2. メトリクス

フェッチ戦略 は、アプリケーションが関連をナビゲートする必要があるときに、 Hibernate が関連オブジェクトを復元するために使用する戦略です。フェッチ戦略は O/R マッピングのメタデータに宣言するか、特定の HQL 、 Criteria クエリでオーバーライドします。

Hibernate3 は次に示すフェッチ戦略を定義しています:

Hibernate は次に示す戦略とも区別をします:

二つの直行する概念があります: いつ 関連をフェッチするか、そして、 どうやって フェッチするか(どんな SQL を使って)。これらを混同しないでください。 fetch はパフォーマンスチューニングに使います。 lazy はあるクラスの分離されたインスタンスのうち、どのデータを常に使用可能にするかの取り決めを定義します。

デフォルトでは、 Hibernate3 はコレクションに対しては遅延セレクトフェッチを使い、単一値関連には遅延プロキシフェッチを使います。これらのデフォルト動作はほぼすべてのアプリケーションのほぼすべての関連で意味があります。

注:hibernate.default_batch_fetch_size をセットしたときは、 Hibernate は遅延フェッチのためのバッチフェッチ最適化を使うでしょう(この最適化はより細かいレベルで有効にすることも出来ます)。

しかし、遅延フェッチは知っておかなければならない一つの問題があります。 Hibernate の session をオープンしているコンテキストの外から遅延関連にアクセスすると、例外が発生します。例:

= 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"/>

マッピング定義で定義した フェッチ 戦略は次のものに影響します:

たとえどんなフェッチ戦略を使ったとしても、遅延ではないグラフはメモリに読み込まれることが保証されます。つまり、特定の 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-oneone-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();

関連も遅延初期化されます。これはプロパティを Cat 型で宣言しなければならないことを意味します。 CatImpl ではありません。

プロキシの初期化を 必要としない 操作も存在します。

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つの基本的な方法があります:

大きなコレクションを初期化したくはないが、コレクションについてのなんらかの情報(サイズのような)やデータのサブセットを必要とすることがあります。

コレクションフィルタを使うことで、初期化せずにコレクションのサイズを取得することが出来ます:

( (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 インスタンスが存在し、それぞれの Catowner である 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 です。

コレクションのバッチフェッチも有効にすることが出来ます。例として、それぞれの PersonCat の遅延コレクションを持っており、 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具体化したパス がよりよい選択になります。)

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 wull 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. Say we have the following mappings:


<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>
</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. Just add the following to your mapping:


<hibernate-mapping>
    ...
    <fetch-profile name="customer-with-orders">
        <fetch entity="Customer" association="orders" style="join"/>
    </fetch-profile>
</hibernate-mapping
>

or even:


<hibernate-mapping>
    <class name="Customer">
        ...
        <fetch-profile name="customer-with-orders">
            <fetch association="orders" style="join"/>
        </fetch-profile>
    </class>
    ...
</hibernate-mapping
>

Now the following code will actually load both the customer and their orders:



    Session session = ...;
    session.enableFetchProfile( "customer-with-orders" );  // name matches from mapping
    Customer customer = (Customer) session.get( Customer.class, customerId );

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 レベル)のキャッシュを設定することが出来ます。クラスタ化されたキャッシュにつなぐことさえ出来ます。しかし注意してください。キャッシュは他のアプリケーションによる永続層の変更を考慮しません(キャッシュデータを定期的に期限切れにする設定は出来ます)。

Hibernate が使用するキャッシュ実装は、 hibernate.cache.provider_class プロパティに org.hibernate.cache.CacheProvider を実装したクラス名を指定することで変更できます。 Hibernate は多くのオープンソースのキャッシュプロバイダをビルトイン実装で持っています(後にリストがあります)。加えて、前に説明したように、あなた自身が独自の実装をして、それを組み込むことも出来ます。バージョン3.2より前では EhCache がデフォルトのキャッシュプロバイダであることに注意してください。バージョン3.2ではこれは当てはまりません。


クラスやコレクションのマッピングの <cache> 要素は以下の形式です。

<cache
    usage="tra(1)nsactional|read-write|nonstrict-read-write|read-only"
    region="Re(2)gionName"
    include="a(3)ll|non-lazy"
/>

1

usage (必須) キャッシング戦略を指定します: transactionalread-writenonstrict-read-write または read-only

2

region (オプション、クラスまたはコレクションのロールネームのデフォルト) 2次レベルのキャッシュ領域の名前を指定します

3

include (オプション、 all に対してデフォルト) non-lazy は、 属性レベルの lazy フェチが有効になっている場合 lazy="true" でマッピングされるエンティティのプロパティはキャッシュされなくてもよいことを指定します。

または(よりよい方法として?)、 hibernate.cfg.xml<class-cache><collection-cache> 要素を指定することも出来ます。

usage 属性は キャッシュの並列性戦略 を指定します。

オブジェクトを save()update()saveOrUpdate() に渡すとき、そして load()get()list()iterate()scroll() を使ってオブジェクトを復元するときには常に、そのオブジェクトは Session の内部キャッシュに追加されます。

次に flush() が呼ばれると、オブジェクトの状態はデータベースと同期化されます。もしこの同期が起こることを望まないときや、膨大な数のオブジェクトを処理していてメモリを効率的に扱う必要があるときは、 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 にはインスタンス、クラス全体、コレクションのインスタンス、コレクション全体をキャッシュから削除するためのメソッドがそれぞれ定義されています。

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 は特定のセッションが二次キャッシュとどのように相互作用するかを指定します。

二次キャッシュの内容やクエリキャッシュ領域を見るために、 Statistics API を使ってください:

Map cacheEntries = sessionFactory.getStatistics()

        .getSecondLevelCacheStatistics(regionName)
        .getEntries();

統計情報を有効にして、さらにオプションとして、キャッシュエントリを人がより理解可能な形式で保持することを Hibernate に強制します:

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:

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.

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 がコレクションの行を更新、削除するために使う主キーの構造もまた考えなければなりません。これは以下の分類を提示します。

すべてのインデックス付きコレクション (マップ、リスト、配列) は <key><index> カラムからなる主キーを持っています。この場合はコレクションの更新は非常に効率的です。主キーは有用なインデックスになり、 Hibernate が特定の行を更新または削除するときに、その行を効率的に見つけることができます。

set は <key> からなる主キーと要素のカラムを持っています。これはコレクション要素のいくつかの型については効率的ではないかもしれません。特に複合要素、大きなテキスト、バイナリフィールドでは非効率です。データベースは複合主キーに効率的にインデックスを付けることができないからです。一方、一対多や多対多関連において、特に人工識別子の場合は同じぐらい効率的です。(余談: SchemaExport で実際に <set> の主キーを作りたいなら、すべてのカラムで not-null="true" を宣言しなければなりません。)

<idbag> マッピングは代理キーを定義します。そのため更新は常に非常に効率的です。事実上、これは最善のケースです。

bag は最悪のケースです。 bag は要素の値の重複が可能で、インデックスカラムを持たないため、主キーは定義されないかもしれません。 Hibernate には重複した行を区別する方法がありません。 Hibernate はこの問題の解決のために、変更があったときには常に完全な削除(一つの DELETE による)を行い、コレクションの再作成を行います。これは非常に非効率的かもしれません。

一対多関連では、「主キー」はデータベースのテーブルの物理的な主キーではないかもしれないことに注意してください。しかしこの場合でさえ、上記の分類はまだ有用です。(Hibernateがコレクションの個々の行をどうやって「見つけるか」を表しています。)

上での議論から、インデックス付きコレクションと(普通の) set は要素の追加、削除、更新でもっとも効率的な操作が出来ることは明らかです。

ほぼ間違いなく、多対多関連や値のコレクションにおいて、インデックス付きコレクションが set よりも優れている点が一つ以上あります。 Set はその構造のために、 Hibernate は要素が「変更」されたときに行を決して UPDATE しません。 Set への変更は常に(個々の行の) INSERTDELETE によって行います。繰り返しますが、これは一対多関連には当てはまりません。

配列は遅延処理ができないという決まりなので、結論として、list、map、idbag がもっともパフォーマンスの良い(inverse ではない)コレクションタイプとなります。 set もそれほど違いはありません。 Hibernate のアプリケーションでは、 set はコレクションのもっとも共通の種類として期待されます。 "set" の表現は関連モデルではもっとも自然だからです。

しかし、よくデザインされた Hibernate のドメインモデルでは、通常もっとも多いコレクションは事実上 inverse="true" を指定した一対多関連です。これらの関連では、更新は多対一の関連端で扱われ、コレクションの更新パフォーマンスの問題は当てはまりません。

時々、コレクションの要素を一つ一つ削除することは極めて非効率的になることがあります。 Hibernate は愚かではないので、新しい空のコレクションの場合( list.clear() を呼び出した場合など)ではこれをすべきでないことを知っています。この場合は、 Hibernate は DELETE を一回発行して、それですべて終わります。

サイズ20のコレクションに一つの要素を追加し、それから二つの要素を削除するとします。 Hibernate は一つの INSERT 文と二つの DELETE 文を発行します (コレクションが bag でなければ)。これは確かに望ましい動作です。

しかし、18個の要素を削除して2つを残し、それから3つ新しい要素を追加するとします。このとき二つの方法があります。

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 に対してモニタリングの開始(終了)を行うことが出来ます。

統計は clear() メソッドを使って手動でリセットすることが出来ます。サマリは logSummary() メソッドを使って logger に送ることが出来ます(info レベルです)。

多くのものがあります。すべての使用可能なカウンタは Statistics インターフェースの API に書かれており、3つの分類があります:

例として、キャッシュのヒット、ヒットミスや、要素、コレクション、クエリの割合、クエリの実行に必要な平均時間を確認できます。ミリ秒の数値は Java の近似を受けることに注意してください。 Hibernate は JVM の精度に制限され、プラットフォームによっては10秒単位でしか正確でないかもしれません。

単純な getter はグローバルメトリクス(すなわち特定のエンティティ、コレクション、キャッシュ領域などに縛られない)にアクセスするために使います。特定のエンティティ、コレクション、キャッシュ領域のメトリクスは、それらの名前や、クエリの HQL 、 SQL 表現によってアクセスすることが出来ます。さらに詳しい情報は、 StatisticsEntityStatisticsCollectionStatisticsSecondLevelCacheStatisticsQueryStatistics 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() メソッドでそれぞれの名前のリストを取得することが出来ます。