第21章 Infinispan Query DSL
21.1. Infinispan Query DSL
Infinispan Query DSL はキャッシュをクエリーするための統一された方法を提供します。これは、ライブラリーモードでインデックス化されたクエリーとインデックスのないクエリーの両方に使用でき、リモートクエリー (Hot Rod Java クライアントを経由) にも使用できます。Infinispan Query DSL では、Lucene ネイティブクエリー API や Hibernate Search クエリー API に依存せずにクエリーを実行できます。
Infinispan Query DSL では、インデックスのないクエリーは JBoss Data Grid リモートおよび埋め込みモードでのみ利用できます。インデックスのないクエリーには設定されたインデックス (「Infinispan Query DSL ベースのクエリー」を参照) は必要ありません。Hibenrate Search や Lucene ベースの API はインデックスのないクエリーを使用できません。
21.2. Infinispan Query DSL を用いたクエリーの作成
新しいクエリー API は org.infinispan.query.dsl パッケージにあります。クエリーは、Search.getQueryFactory()
を使用して取得される QueryFactory
インスタンスを利用して作成されます。各 QueryFactory
インスタンスは 1 つのキャッシュインスタンスにバインドされ、複数の並列クエリーを作成する場合に使用できるステートレスでスレッドセーフなオブジェクトです。
Infinispan Query DSL は以下の手順にしたがってクエリーを実行します。
-
クエリーは
from(Class entityType)
メソッドを呼び出して作成されます。これは、特定のキャッシュから指定のエンティティークラスのクエリーを作成するQueryBuilder
オブジェクトを返します。 -
QueryBuilder
は、DSL メソッドを呼び出して指定される検索基準と設定を累積します。また、構築を完了するQueryBuilder.build()
メソッドを呼び出してQuery
オブジェクトを構築するために使用されます。QueryBuilder
オブジェクトを使用して、入れ子のクエリー以外の複数のクエリーを同時に構築することはできませんが、後で再使用することができます。 -
Query
オブジェクトのlist()
メソッドを呼び出してクエリーを実行し、結果を取得します。実行するとQuery
オブジェクトは再使用できなくなります。新しい結果を取得する必要がある場合は、QueryBuilder.build()
を呼び出して新しいインスタンスを取得します。
クエリーは単一のエンティティー型を対象とし、単一キャッシュの内容全体で評価されます。複数キャッシュでのクエリーの実行や、複数のエンティティー型を対象とするクエリーの作成はサポートされません。
21.3. Infinispan Query DSL ベースのクエリーの有効化
ライブラリーモードでは、Infinispan Query DSL ベースのクエリーの実行は Lucene ベースの API クエリーの実行とほぼ同じです。前提条件は次のとおりです。
- Infinispan Query に必要なすべてのライブラリーがクラスパスにある。詳細は 『Administration and Configuration Guide』を参照してください。
- インデックス化が有効で、キャッシュに対して設定されている (任意)。詳細は、『Administration and Configuration Guide』を参照してください。
- アノテーションが付いた POJO キャッシュ値 (任意)。インデックス化が有効になっていない場合は POJO アノテーションは必要なく、設定されている場合は無視されます。インデックス化が有効になっていない場合、Hibernate Search のアノテーションが付いたフィールドのみではなく、JavaBeans の慣例にしたがうフィールドすべてが検索可能になります。
21.4. Infinispan Query DSL ベースのクエリーの実行
Infinispan Query DSL ベースのクエリーが有効になったら、DSL ベースのクエリーを実行するために Search
から QueryFactory
を取得します。
キャッシュの QueryFactory の取得
ライブラリーモードで、以下のように QueryFactory
を取得します。
QueryFactory qf = org.infinispan.query.Search.getQueryFactory(cache)
DSL ベースのクエリーの構築
import org.infinispan.query.Search; import org.infinispan.query.dsl.QueryFactory; import org.infinispan.query.dsl.Query; QueryFactory qf = Search.getQueryFactory(cache); Query q = qf.from(User.class) .having("name").eq("John") .build(); List list = q.list(); assertEquals(1, list.size()); assertEquals("John", list.get(0).getName()); assertEquals("Doe", list.get(0).getSurname());
リモートクエリーをリモートクライアントサーバーモードで使用する場合、Search
は org.infinispan.client.hotrod
パッケージにあります。詳細は 「Hot Rod Java クライアント経由のリモートクエリーの実行」の例を参照してください。
複数の条件 (サブ条件を含む) をブール値演算子と組み合わせることも可能です。
複数条件の組み合わせ
Query q = qf.from(User.class) .having("name").eq("John") .and().having("surname").eq("Doe") .and().not(qf.having("address.street").like("%Tanzania%") .or().having("address.postCode").in("TZ13", "TZ22")) .build();
このクエリー API は、ユーザーに Lucene クエリーオブジェクト構築の詳細内容を公開しないことで、クエリーの作成方法を簡素化します。また、リモート Hot Rod クライアントが利用できる利点もあります。
以下の例は、Book
エンティティーのクエリーの書き方を示しています。
Book
エンティティーのクエリー
import org.infinispan.query.Search; import org.infinispan.query.dsl.*; // get the DSL query factory, to be used for constructing the Query object: QueryFactory qf = Search.getQueryFactory(cache); // create a query for all the books that have a title which contains the word "engine": Query query = qf.from(Book.class) .having("title").like("%engine%") .build(); // get the results List<Book> list = query.list();
21.5. 射影クエリー
多くの場合で、完全なドメインオブジェクトを返す必要はなく、アプリケーションには属性の小さなサブセットのみが必要になります。射影クエリーでは、属性 (または属性のパス) の特定のサブセットを返すことができます。射影クエリーが使用されると、Query.list()
はドメインエンティティー全体 (List<Object>
) を返さずに、アレイの各エントリーが射影された属性に対応する List<Object[]>
を返します。
射影クエリーを定義するには、以下の例のようにクエリーの構築時に select(…)
メソッドを使用します。
書名と発行年の取得
// Match all books that have the word "engine" in their title or description // and return only their title and publication year. Query query = queryFactory.from(Book.class) .select(Expression.property("title"), Expression.property("publicationYear")) .having("title").like("%engine%") .or().having("description").like("%engine%") .build(); // results.get(0)[0] contains the first matching entry's title // results.get(0)[1] contains the first matching entry's publication year List<Object[]> results = query.list();
21.6. グループ化および集約操作
Infinispan Query DSL には、グループ化フィールドのセットを基にしてクエリー結果をグループ化する機能や、値のセットに集約関数を適用して各グループからの結果の集約を構築する機能があります。グループ化と集約は、射影クエリーとのみ使用できます。
グループ化フィールドのセットは、groupBy(field)
メソッドを複数回呼び出して指定されます。グループ化フィールドの順番は関係ありません。
射影で選択されたすべての非グループ化フィールドは、以下に説明するグループ化関数の 1 つを使用して集約する必要があります。
著者による本のグループ化とその冊数
Query query = queryFactory.from(Book.class) .select(Expression.property("author"), Expression.count("title")) .having("title").like("%engine%") .groupBy("author") .build(); // results.get(0)[0] will contain the first matching entry's author // results.get(0)[1] will contain the first matching entry's title List<Object[]> results = query.list();
集約操作
以下の集約操作を指定のフィールドで実行できます。
-
avg()
-Number
のセットの平均を算出し、Double
として表します。null 以外の値がない場合、結果は null になります。 -
count()
- null でない行の数をLong
として返します。null 以外の値がない場合、結果は 0 になります。 max()
- 指定のフィールドで見つかった最も大きな値と、適用されたフィールドと同じ戻り値の型を返します。null 以外の値がない場合は、結果は null になります。注記指定のフィールドの値は、
Comparable
型である必要があります。そうでないとIllegalStateException
が発生します。min()
- 指定のフィールドで見つかった最も小さな値と、適用されたフィールドと同じ戻り値の型を返します。null 以外の値がない場合は、結果は null になります。注記指定のフィールドの値は、
Comparable
型である必要があります。そうでないとIllegalStateException
が発生します。sum()
-Number
のセットの合計を算出し、返します。戻り値の型は指定のフィールド型によります。null で以外の値がない場合、結果は null になります。以下の表は、指定のフィールドを基にした戻り値の型を表しています。
表21.1 戻り値の型の合計 フィールド型 戻り値の型 Integral (
BigInteger
以外)Long
Floating Point
Double
BigInteger
BigInteger
BigDecimal
BigDecimal
射影クエリーの特別なケース
射影クエリーでは、以下のケースは特別なユースケースとなります。
- 選択されたフィールドがすべて集約され、グループ化に何も使用されないことが適切である射影クエリー。この場合、集約はグループごとに算出されず、グローバルに算出されます。
- フィールドのグループ化を集約で使用できる。これは、集約が単一のデータポイント上で算出され、値が現在のグループに属する退化したケースになります。
- グループ化フィールドのみを選択し、集約フィールドは選択しないことが適切であるクエリー。
グループ化および集約クエリーの評価
通常のクエリーのように、集約クエリーにフィルター条件を含めることができます。これは、任意でグループ化操作の前後に実行できます。
groupBy
メソッドの呼び出し前に指定されたすべてのフィルター条件は、グループ化操作の実行前に直接キャッシュエントリーに適用されます。これらのフィルター条件は、クエリーされたエンティティー型のプロパティーを参照することがあり、後でグループ化に使用されるデータセットを制限するためのものです。
groupBy
メソッドの呼び出し後に指定されたすべてのフィルター条件は、グループ化操作によって生じる射影に適用されます。これらのフィルター条件は、 groupBy
によって指定されたフィールドまたは集約フィールドを参照できます。select
句に指定されていない集約フィールドの参照は許可されますが、非集約フィールドや非グループ化フィールドの参照は禁止されます。このフェーズをフィルターすると、プロパティーを基にしてグループの数が削減されます。
順序付けも通常のクエリーと同様に指定できます。順序付け操作はグループ化操作の後に実行され、前述のとおりグループ化後のフィルターによって許可されるフィールドを参照できます。
21.7. 名前付きパラメーターの使用
各リクエストに新しいクエリーを作成する代わりに、各実行で置き換えられるパラメーターを含むことが可能です。これにより、クエリーを 1 度に定義でき、必要時にクエリーの変数を調整できます。
having(…)
の比較演算子の右側に Expression.param(…)
演算子を使用すると、クエリーの作成時パラメーターが定義されます。
名前付きパラメーターの定義
import org.infinispan.query.Search; import org.infinispan.query.dsl.*; [...] QueryFactory queryFactory = Search.getQueryFactory(cache); // Defining a query to search for various authors Query query = queryFactory.from(Book.class) .select("title") .having("author").eq(Expression.param("authorName")) .build() [...]
名前付きパラメーターの値設定
デフォルトでは、宣言されたパラメーターはすべて null となり、クエリーの実行前に定義されたすべてのパラメーターを null でない値に更新する必要があります。パラメーターの宣言後、クエリーで新しい値を指定して setParameter(parameterName, value)
または setParameters(parameterMap)
を呼び出すと値を更新することができます。また、クエリーを再構築する必要はありません。新しいパラメーターが定義された後に再度実行することができます。
パラメーターを個別に更新
[...] query.setParameter("authorName","Smith"); // Rerun the query and update the results resultList = query.list(); [...]
パラメーターのマップとして更新
[...] parameterMap.put("authorName","Smith"); query.setParameters(parameterMap); // Rerun the query and update the results resultList = query.list(); [...]