第18章 Infinispan Query DSL

18.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 はインデックスのないクエリーを使用できません。

18.2. Infinispan Query DSL を用いたクエリーの作成

新しいクエリー API は org.infinispan.query.dsl パッケージにあります。クエリーは、Search.getQueryFactory() を使用して取得される QueryFactory インスタンスを利用して作成されます。各 QueryFactory インスタンスは 1 つのキャッシュインスタンスにバインドされ、複数の並列クエリーを作成する場合に使用できるステートレスでスレッドセーフなオブジェクトです。

Infinispan Query DSL は以下の手順にしたがってクエリーを実行します。

  1. クエリーは from(Class entityType) メソッドを呼び出して作成されます。これは、特定のキャッシュから指定のエンティティークラスのクエリーを作成する QueryBuilder オブジェクトを返します。
  2. QueryBuilder は、DSL メソッドを呼び出して指定される検索基準と設定を累積します。また、構築を完了する QueryBuilder.build() メソッドを呼び出して Query オブジェクトを構築するために使用されます。QueryBuilder オブジェクトを使用して、入れ子のクエリー以外の複数のクエリーを同時に構築することはできませんが、後で再使用することができます。
  3. Query オブジェクトの list() メソッドを呼び出してクエリーを実行し、結果を取得します。実行すると Query オブジェクトは再使用できなくなります。新しい結果を取得する必要がある場合は、QueryBuilder.build() を呼び出して新しいインスタンスを取得します。
重要

クエリーは単一のエンティティー型を対象とし、単一キャッシュの内容全体で評価されます。複数キャッシュでのクエリーの実行や、複数のエンティティー型を対象とするクエリーの作成はサポートされません。

18.3. Infinispan Query DSL ベースのクエリーの有効化

ライブラリーモードでは、Infinispan Query DSL ベースのクエリーの実行は Lucene ベースの API クエリーの実行とほぼ同じです。前提条件は次のとおりです。

  • Infinispan Query に必要なすべてのライブラリーがクラスパスにある。詳細は Administration and Configuration Guide を参照してください。
  • インデックス化が有効で、キャッシュに対して設定されている (任意)。詳細は、 Administration and Configuration Guide を参照してください。
  • アノテーションが付いた POJO キャッシュ値 (任意)。インデックス化が有効になっていない場合は POJO アノテーションは必要なく、設定されている場合は無視されます。インデックス化が有効になっていない場合、Hibernate Search のアノテーションが付いたフィールドのみではなく、JavaBeans の慣例にしたがうフィールドすべてが検索可能になります。

18.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());

リモートクエリーをリモートクライアントサーバーモードで使用する場合、Searchorg.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();

18.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();

18.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 になります。

    以下の表は、指定のフィールドを基にした戻り値の型を表しています。

    表18.1 戻り値の型の合計

    フィールド型戻り値の型

    Integral (BigInteger 以外)

    Long

    Floating Point

    Double

    BigInteger

    BigInteger

    BigDecimal

    BigDecimal

射影クエリーの特別なケース

射影クエリーでは、以下のケースは特別なユースケースとなります。

  • 選択されたフィールドがすべて集約され、グループ化に何も使用されないことが適切である射影クエリー。この場合、集約はグループごとに算出されず、グローバルに算出されます。
  • フィールドのグループ化を集約で使用できる。これは、集約が単一のデータポイント上で算出され、値が現在のグループに属する退化したケースになります。
  • グループ化フィールドのみを選択し、集約フィールドは選択しないことが適切であるクエリー。

グループ化および集約クエリーの評価

通常のクエリーのように、集約クエリーにフィルター条件を含めることができます。これは、任意でグループ化操作の前後に実行できます。

groupBy メソッドの呼び出し前に指定されたすべてのフィルター条件は、グループ化操作の実行前に直接キャッシュエントリーに適用されます。これらのフィルター条件は、クエリーされたエンティティー型のプロパティーを参照することがあり、後でグループ化に使用されるデータセットを制限するためのものです。

groupBy メソッドの呼び出し後に指定されたすべてのフィルター条件は、グループ化操作によって生じる射影に適用されます。これらのフィルター条件は、 groupBy によって指定されたフィールドまたは集約フィールドを参照できます。select 句に指定されていない集約フィールドの参照は許可されますが、非集約フィールドや非グループ化フィールドの参照は禁止されます。このフェーズをフィルターすると、プロパティーを基にしてグループの数が削減されます。

順序付けも通常のクエリーと同様に指定できます。順序付け操作はグループ化操作の後に実行され、前述のとおりグループ化後のフィルターによって許可されるフィールドを参照できます。

18.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();
[...]