30.7.3. Read-ahead
JBoss での最適化ローディングは read-ahead と呼ばれます。これは、ロードするエンティティの行と、その次の数行を読み込む技術のことで、こういった背景から read-ahead (先読み) という表現となっています。JBoss は主に 2 つのストラテジー (
on-find
および on-load
) を実装して、前項で挙げたローディングの問題を最適化します。read-ahead 中にロードされた余分なデータは、すぐにメモリ内のエンティティオブジェクトと関連付けられません。なぜなら、JBoss においてエンティティは実際にアクセスされるまで実体化されないからです。その代わり、これはプレロードキャッシュに格納され、エンティティにロードされるまで、あるいはトランザクションの終わりが現れるまで、そこにとどまります。以降のセクションでは、read-ahead ストラテジーについて説明します。
30.7.3.1. on-find
on-find
ストラテジーは、クエリが呼び出されると、追加のカラムを読み込みます。クエリが on-find
最適化されている場合、クエリが実行されると JBoss は次のクエリを実行します。
SELECT t0_g.id, t0_g.name, t0_g.nick_name, t0_g.badness FROM gangster t0_g ORDER BY t0_g.id ASC
必要なデータはすべてプレロードキャッシュ内に入るので、クエリ結果を反復している間、追加のクエリを実行する必要はありません。このストラテジーは、少量のデータを返すクエリに対しては有効ですが、メモリに大量の結果セットをロードしようとしている場合には非常に効率的ではありません。次のテーブルは、このクエリーの実行を示したものです。
表30.2 on-find 最適化クエリ実行
id | name | nick_name | badness | hangout | organization |
---|---|---|---|---|---|
0 | Yojimbo | Bodyguard | 7 | 0 | Yakuza |
1 | Takeshi | Master | 10 | 1 | Yakuza |
2 | Yuriko | Four finger | 4 | 2 | Yakuza |
3 | Chow | Killer | 9 | 3 | Triads |
4 | Shogi | Lightning | 8 | 4 | Triads |
5 | Valentino | Pizza-Face | 4 | 5 | Mafia |
6 | Toni | Toothless | 2 | 6 | Mafia |
7 | Corleone | Godfather | 6 | 7 | Mafia |
クエリーの
read-ahead
ストラテジーと load-group
は、query
要素に定義されます。read-ahead
ストラテジーが query
要素に宣言されていない場合は、entity
要素または defaults
要素に宣言されたストラテジーが使用されます。on-find
設定は次のとおりです。
<jbosscmp-jdbc> <enterprise-beans> <entity> <ejb-name>GangsterEJB</ejb-name> <!--...--> <query> <query-method> <method-name>findAll_onfind</method-name> <method-params/> </query-method> <jboss-ql><![CDATA[ SELECT OBJECT(g) FROM gangster g ORDER BY g.gangsterId ]]></jboss-ql> <read-ahead> <strategy>on-find</strategy> <page-size>4</page-size> <eager-load-group>basic</eager-load-group> </read-ahead> </query> </entity> </enterprise-beans> </jbosscmp-jdbc>
on-find
ストラテジーには、選択されたすべてのエンティティについて追加データをロードしなければならないという問題があります。Web アプリケーションでは通常、ページ上で固定された数の結果だけがレンダリングされます。プレロードされたデータはトランザクションの長さだけ有効であり、1 トランザクションは Web HTTP の 1 ヒットに制限されるので、プレロードされたデータの大半は使用されません。次項で説明する on-load
ストラテジーでは、この問題の影響を受けません。
30.7.3.1.1. Left join read ahead
Left join read ahead は、
on-find
read-ahead
ストラテジーが強化されたものです。1 つの SQL クエリで、ベースインスタンスからのフィールドだけでなく、CMR ナビゲーションによりベースインスタンスから到達できる関連インスタンスもプレロードすることができます。CMR ナビゲーションの深さに対する制限はありません。また、ナビゲーションおよびリレーションシップタイプのマッピングで使用される CMR フィールドのカーディナリティに対する制限もありません。すなわち、外部キーと関連テーブルマッピングスタイルの両方がサポートされます。それでは、例をいくつか見ていきましょう。エンティティとリレーションシップの宣言を以下に示しています。
30.7.3.1.2. D#findByPrimaryKey
エンティティ
D
があるとします。findByPrimaryKey
に対して生成される一般的な SQL は、次のようになります。
SELECT t0_D.id, t0_D.name FROM D t0_D WHERE t0_D.id=?
findByPrimaryKey
の実行中に、2 つのコレクション値 CMR フィールド bs
および cs
もプレロードすることにします。
<query> <query-method> <method-name>findByPrimaryKey</method-name> <method-params> <method-param>java.lang.Long</method-param> </method-params> </query-method> <jboss-ql><![CDATA[SELECT OBJECT(o) FROM D AS o WHERE o.id = ?1]]></jboss-ql> <read-ahead> <strategy>on-find</strategy> <page-size>4</page-size> <eager-load-group>basic</eager-load-group> <left-join cmr-field="bs" eager-load-group="basic"/> <left-join cmr-field="cs" eager-load-group="basic"/> </read-ahead> </query>
left-join
では、一括読み込みを行いたいリレーションを宣言します。生成される SQL は次のようになります。
SELECT t0_D.id, t0_D.name, t1_D_bs.id, t1_D_bs.name, t2_D_cs.id, t2_D_cs.name FROM D t0_D LEFT OUTER JOIN B t1_D_bs ON t0_D.id=t1_D_bs.D_FK LEFT OUTER JOIN C t2_D_cs ON t0_D.id=t2_D_cs.D_FK WHERE t0_D.id=?
固有の id を持つ
D
では、それに関連するすべての B
と C
をプリロードし、それをデータベースからではなく先読みキャッシュからロードするインスタンスにアクセスすることができます。
30.7.3.1.3. D#findAll
同様に、
D
への findAll
メソッドはD
関連をすべて選択しますが、この findAll
メソッドを最適化することができます。通常の findAll クエリは次のようになります。
SELECT DISTINCT t0_o.id, t0_o.name FROM D t0_o ORDER BY t0_o.id DESC
リレーションをプレロードするには、
left-join
要素をクエリーに追加するだけで結構です。
<query> <query-method> <method-name>findAll</method-name> </query-method> <jboss-ql><![CDATA[SELECT DISTINCT OBJECT(o) FROM D AS o ORDER BY o.id DESC]]></jboss-ql> <read-ahead> <strategy>on-find</strategy> <page-size>4</page-size> <eager-load-group>basic</eager-load-group> <left-join cmr-field="bs" eager-load-group="basic"/> <left-join cmr-field="cs" eager-load-group="basic"/> </read-ahead> </query>
生成される SQL は次のとおりです。
SELECT DISTINCT t0_o.id, t0_o.name, t1_o_bs.id, t1_o_bs.name, t2_o_cs.id, t2_o_cs.name FROM D t0_o LEFT OUTER JOIN B t1_o_bs ON t0_o.id=t1_o_bs.D_FK LEFT OUTER JOIN C t2_o_cs ON t0_o.id=t2_o_cs.D_FK ORDER BY t0_o.id DESC
これで、このシンプルな
findAll
クエリは、各 D
オブジェクトに対して、関連する B
および C
オブジェクトをプレロードするようになりました。
30.7.3.1.4. A#findAll
それでは、より複雑な設定を見てみましょう。ここでは、いくつかのリレーションと共にインスタンス
A
をプレロードしようと思います。
- CMR フィールド
parent
でA
から到達するその親 (セルフリレーション) - CMR フィールド
b
でA
から到達するB
、および CMR フィールドc
でB
から到達する関連のC
- 今回は CMR フィールド b2 で
A
から到達するB
、およびそれに関連付けられた、CMR フィールド c で B から到達するC
参考までに、標準のクエリーは次のとおりです。
SELECT t0_o.id, t0_o.name FROM A t0_o ORDER BY t0_o.id DESC FOR UPDATE
次のメタデータは、プレロードプランを記述したものです。
<query> <query-method> <method-name>findAll</method-name> </query-method> <jboss-ql><![CDATA[SELECT OBJECT(o) FROM A AS o ORDER BY o.id DESC]]></jboss-ql> <read-ahead> <strategy>on-find</strategy> <page-size>4</page-size> <eager-load-group>basic</eager-load-group> <left-join cmr-field="parent" eager-load-group="basic"/> <left-join cmr-field="b" eager-load-group="basic"> <left-join cmr-field="c" eager-load-group="basic"/> </left-join> <left-join cmr-field="b2" eager-load-group="basic"> <left-join cmr-field="c" eager-load-group="basic"/> </left-join> </read-ahead> </query>
生成 SQL は次のようになります。
SELECT t0_o.id, t0_o.name, t1_o_parent.id, t1_o_parent.name, t2_o_b.id, t2_o_b.name, t3_o_b_c.id, t3_o_b_c.name, t4_o_b2.id, t4_o_b2.name, t5_o_b2_c.id, t5_o_b2_c.name FROM A t0_o LEFT OUTER JOIN A t1_o_parent ON t0_o.PARENT=t1_o_parent.id LEFT OUTER JOIN B t2_o_b ON t0_o.B_FK=t2_o_b.id LEFT OUTER JOIN C t3_o_b_c ON t2_o_b.C_FK=t3_o_b_c.id LEFT OUTER JOIN B t4_o_b2 ON t0_o.B2_FK=t4_o_b2.id LEFT OUTER JOIN C t5_o_b2_c ON t4_o_b2.C_FK=t5_o_b2_c.id ORDER BY t0_o.id DESC FOR UPDATE
この設定では、別のデータベースをロードすることなく、見つかった
A
のインスタンスから CMR をナビゲートすることができます。
30.7.3.1.5. A#findMeParentGrandParent
次に別のセルフリレーションの例を示します。あるインスタンス、その親、祖父母および曽祖父母を 1 つのクエリでプレロードするメソッドを書くことにします。これを行うのに、ネストされた
left-join
宣言を使用します。
<query> <query-method> <method-name>findMeParentGrandParent</method-name> <method-params> <method-param>java.lang.Long</method-param> </method-params> </query-method> <jboss-ql><![CDATA[SELECT OBJECT(o) FROM A AS o WHERE o.id = ?1]]></jboss-ql> <read-ahead> <strategy>on-find</strategy> <page-size>4</page-size> <eager-load-group>*</eager-load-group> <left-join cmr-field="parent" eager-load-group="basic"> <left-join cmr-field="parent" eager-load-group="basic"> <left-join cmr-field="parent" eager-load-group="basic"/> </left-join> </left-join> </read-ahead> </query>
生成される SQL は次のようになります。
SELECT t0_o.id, t0_o.name, t0_o.secondName, t0_o.B_FK, t0_o.B2_FK, t0_o.PARENT, t1_o_parent.id, t1_o_parent.name, t2_o_parent_parent.id, t2_o_parent_parent.name, t3_o_parent_parent_parent.id, t3_o_parent_parent_parent.name FROM A t0_o LEFT OUTER JOIN A t1_o_parent ON t0_o.PARENT=t1_o_parent.id LEFT OUTER JOIN A t2_o_parent_parent ON t1_o_parent.PARENT=t2_o_parent_parent.id LEFT OUTER JOIN A t3_o_parent_parent_parent ON t2_o_parent_parent.PARENT=t3_o_parent_parent_parent.id WHERE (t0_o.id = ?) FOR UPDATE
left-join
メタデータを取り除けば、次のようになります。
SELECT t0_o.id, t0_o.name, t0_o.secondName, t0_o.B2_FK, t0_o.PARENT FOR UPDATE