14.2. 埋め込みクエリー
Data Grid をライブラリーとして使用すると、埋め込みクエリーを使用できます。protobuf マッピングは不要であり、インデックス作成と検索の両方が Java オブジェクト上で実行されます。ライブラリーモードでは、Lucene クエリーを直接実行し、利用可能なすべての Query API を使用できます。また、柔軟なインデックス設定により、レイテンシーを最小限に抑えることができます。
14.2.1. 簡単な例
books と呼ばれる Data Grid キャッシュに Book インスタンスを保存します。Book インスタンスはインデックス化されるため、キャッシュのインデックスを有効にし、Data Grid が インデックスを自動的に設定 できるようにします。
Data Grid の設定:
infinispan.xml
<infinispan> <cache-container> <transport cluster="infinispan-cluster"/> <distributed-cache name="books"> <indexing index="PRIMARY_OWNER" auto-config="true"/> </distributed-cache> </cache-container> </infinispan>
キャッシュを取得します。
import org.infinispan.Cache; import org.infinispan.manager.DefaultCacheManager; import org.infinispan.manager.EmbeddedCacheManager; EmbeddedCacheManager manager = new DefaultCacheManager("infinispan.xml"); Cache<String, Book> cache = manager.getCache("books");
各 Book は、次の例のように定義されます。インデックスを作成するプロパティーを選択する必要があります。プロパティーごとに、Hibernate Search プロジェクトで定義されたアノテーションを使用して高度なインデックスオプションを任意で選択できます。
Book.java
import org.hibernate.search.annotations.*; import java.util.Date; import java.util.HashSet; import java.util.Set; //Values you want to index need to be annotated with @Indexed, then you pick which fields and how they are to be indexed: @Indexed public class Book { @Field String title; @Field String description; @Field @DateBridge(resolution=Resolution.YEAR) Date publicationYear; @IndexedEmbedded Set<Author> authors = new HashSet<Author>(); }
Author.java
public class Author { @Field String name; @Field String surname; // hashCode() and equals() omitted }
Data Grid Cache に複数の Book インスタンスを保存したとすると、次の例のように、一致するフィールドを検索できます。
Lucene クエリーの使用:
// get the search manager from the cache: SearchManager searchManager = org.infinispan.query.Search.getSearchManager(cache); // create any standard Lucene query, via Lucene's QueryParser or any other means: org.apache.lucene.search.Query fullTextQuery = //any Apache Lucene Query // convert the Lucene query to a CacheQuery: CacheQuery cacheQuery = searchManager.getQuery( fullTextQuery ); // get the results: List<Object> found = cacheQuery.list();
Lucene クエリーは、"title:infinispan AND authors.name:sanne" などのテキスト形式でクエリーを解析するか、Hibernate Search によって提供されるクエリービルダーを使用して作成されます。
// get the search manager from the cache: SearchManager searchManager = org.infinispan.query.Search.getSearchManager( cache ); // you could make the queries via Lucene APIs, or use some helpers: QueryBuilder queryBuilder = searchManager.buildQueryBuilderForClass(Book.class).get(); // the queryBuilder has a nice fluent API which guides you through all options. // this has some knowledge about your object, for example which Analyzers // need to be applied, but the output is a fairly standard Lucene Query. org.apache.lucene.search.Query luceneQuery = queryBuilder.phrase() .onField("description") .andField("title") .sentence("a book on highly scalable query engines") .createQuery(); // the query API itself accepts any Lucene Query, and on top of that // you can restrict the result to selected class types: CacheQuery query = searchManager.getQuery(luceneQuery, Book.class); // and there are your results! List objectList = query.list(); for (Object book : objectList) { System.out.println(book); }
list() とは別に、結果をストリーミングするか、ページネーションを使用できます。
Lucene またはフルテキスト機能を必要としない検索で、ほとんどの場合集計と完全一致に関するものである場合は、Data Grid Query DSL API を使用できます。
import org.infinispan.query.dsl.QueryFactory; import org.infinispan.query.dsl.Query; import org.infinispan.query.Search; // get the query factory: QueryFactory queryFactory = Search.getQueryFactory(cache); Query q = queryFactory.from(Book.class) .having("author.surname").eq("King") .build(); List<Book> list = q.list();
最後に、Ickle クエリーを直接使用して、1 つ以上の述語で Lucene 構文を可能にします。
import org.infinispan.query.dsl.QueryFactory; import org.infinispan.query.dsl.Query; // get the query factory: QueryFactory queryFactory = Search.getQueryFactory(cache); Query q = queryFactory.create("from Book b where b.author.name = 'Stephen' and " + "b.description : (+'dark' -'tower')"); List<Book> list = q.list();
14.2.2. インデックス化
Data Grid のインデックス作成はキャッシュごとに行われ、デフォルトではキャッシュはインデックス化されません。インデックスを有効にすることは必須ではありませんが、インデックスを使用したクエリーのパフォーマンスは大幅に高くなります。一方、インデックスを有効にするとクラスターの書き込みスループットに悪影響を及ぼす可能性があるため、一部のストラテジーの クエリーパフォーマンスガイド を確認して、キャッシュタイプとユースケースに応じてこの影響を最小限に抑えるようにしてください。
14.2.2.1. 設定
14.2.2.1.1. 一般的な形式
XML によるインデックス作成を有効にするには、<indexing>
要素と index
(インデックスモード) をキャッシュ設定に追加し、オプションで追加のプロパティーを渡す必要があります。
<infinispan> <cache-container default-cache="default"> <replicated-cache name="default"> <indexing index="ALL"> <property name="property.name">some value</property> </indexing> </replicated-cache> </cache-container> </infinispan>
プログラマティック
import org.infinispan.configuration.cache.*; ConfigurationBuilder cacheCfg = ... cacheCfg.indexing().index(Index.ALL) .addProperty("property name", "propery value")
14.2.2.1.2. Index names
index
要素内の各プロパティーの前には、org.infinispan.sample.Car
というインデックス名が付けられます。directory_provider
は local-heap
です。
... <indexing index="ALL"> <property name="org.infinispan.sample.Car.directory_provider">local-heap</property> </indexing> ... </infinispan>
cacheCfg.indexing() .index(Index.ALL) .addProperty("org.infinispan.sample.Car.directory_provider", "local-heap")
Data Grid は、キャッシュに存在する各エンティティーのインデックスを作成し、これらのインデックスを個別に設定できます。@Indexed
アノテーションが付けられたクラスの場合、アノテーションの name
引数で上書きされない限り、インデックス名は完全修飾クラス名になります。
以下のスニペットでは、すべてのエンティティーのデフォルトストレージは infinispan
ですが、Boat
インスタンスは boatIndex
という名前のインデックスの local-heap
に保存されます。Airplane
エンティティーも local-heap
に保存されます。他のエンティティーのインデックスは、default
で接頭辞が付けられたプロパティーで設定されます。
package org.infinispan.sample; @Indexed(name = "boatIndex") public class Boat { } @Indexed public class Airplane { }
... <indexing index="ALL"> <property name="default.directory_provider">infinispan</property> <property name="boatIndex.directory_provider">local-heap</property> <property name="org.infinispan.sample.Airplane.directory_provider"> ram </property> </indexing> ... </infinispan>
14.2.2.1.3. インデックス化されたエンティティーの指定
Data Grid は、キャッシュ内の異なるエンティティータイプのインデックスを自動的に認識し、管理できます。Data Grid の今後のバージョンではこの機能が削除されるため、インデックス化されるタイプを宣言することが推奨されます(完全修飾クラス名で一覧表示します)。これは、xml を介して実行できます。
<infinispan> <cache-container default-cache="default"> <replicated-cache name="default"> <indexing index="ALL"> <indexed-entities> <indexed-entity>com.acme.query.test.Car</indexed-entity> <indexed-entity>com.acme.query.test.Truck</indexed-entity> </indexed-entities> </indexing> </replicated-cache> </cache-container> </infinispan>
プログラムを使用する場合
cacheCfg.indexing() .index(Index.ALL) .addIndexedEntity(Car.class) .addIndexedEntity(Truck.class)
サーバーモードでは、indexed-entities 要素に一覧表示されているクラス名は、JBoss Modules モジュール識別子、スロット名、および完全修飾クラス名で設定される拡張クラス名を使用し、これら 3 つのコンポーネントは ':' 文字 (例: "com.acme.my-module-with-entity-classes:my-slot:com.acme.query.test.Car") を使用する必要があります。エンティティークラスは参照モジュールに配置する必要があります。このモジュールは、サーバーの modules フォルダーにデプロイされたユーザー提供モジュールか、deployments フォルダーにデプロイされたプレーン jar のいずれかです。問題のモジュールはキャッシュの自動依存関係となるため、最終的に再デプロイするとキャッシュが再起動されます。
サーバーの場合のみ、extended クラス名の使用要件に従い、プレーンクラス名を使用すると、誤った ClassLoader が使用されているため、クラスがないために解決に失敗します(Data Grid の内部クラスパスが使用されます)。
14.2.2.2. インデックスモード
Data Grid ノードは通常、ローカルとリモートの 2 つのソースからデータを受け取ります。ローカルは、同じ JVM のマップ API を使用してデータを操作するクライアントに変換されます。リモートデータは、レプリケーションまたはリバランス時に他の Data Grid ノードから提供されます。
インデックスモードの設定は、クラスターがインデックス化されるビューのノードから定義します。
値:
- all: すべてのデータがインデックス化され、ローカル、およびリモートになります。
- LOCAL: ローカルデータのみがインデックス化されます。
- PRIMARY_OWNER: ローカルまたはリモートの作成元に関係なく、ノードがプライマリー所有者であるキーを含むエントリーのみがインデックス化されます。
- NONE: データがインデックス化されません。インデックスを設定しないのと同等です。
14.2.2.3. インデックスマネージャー
インデックスマネージャーは、Data Grid クエリーの central コンポーネントで、Lucene の IndexReader や IndexWriter などの複数のクエリーコンポーネントのインデックス設定、配布、および内部ライフサイクルを行います。各インデックスマネージャーは Directory Provider に関連付けられており、インデックスの物理ストレージを定義します。
インデックスの分散に関して、Data Grid は共有インデックスまたは非共有インデックスで設定できます。
14.2.2.6. 外部インデックス
Data Grid 自体によって管理されている共有インデックスと非共有インデックスを持つこと以外に、サードパーティーの検索エンジンにインデックスをオフロードすることができます。現在、Data Grid は Elasticsearch を外部インデックスストレージとしてサポートします。
14.2.2.6.1. Elasticsearch IndexManager (実験的)
このインデックスマネージャーは、すべてのインデックスを外部 Elasticsearch サーバーに転送します。これは実験的な統合であり、一部の機能は利用できません。たとえば、@IndexedEmbedded
アノテーションの indexNullAs
は 現在サポートされていません。
設定:
<indexing index="PRIMARY_OWNER"> <property name="default.indexmanager">elasticsearch</property> <property name="default.elasticsearch.host">link:http://elasticHost:9200</property> <!-- other elasticsearch configurations --> </indexing>
Data Grid は Elasticsearch を単一の共有インデックスとみなすため、インデックスモードは LOCAL
に設定する必要があります。設定プロパティーの完全な説明など、Elasticsearch 統合の詳細は、Hibernate Search マニュアル を参照してください。
14.2.2.7. 自動設定
属性 auto-config は、キャッシュタイプに基づいてインデックスを設定する簡単な方法を提供します。レプリケートされたキャッシュとローカルキャッシュの場合、インデックスは、他のプロセスと共有されず、ディスク上で永続化されるように設定されます。また、オブジェクトがインデックス化されてから、検索に使用可能な時点 (ほぼリアルタイム) の間に最小遅延が発生するように設定されます。
<local-cache name="default"> <indexing index="PRIMARY_OWNER" auto-config="true"/> </local-cache>
auto-config を介して追加されたプロパティーを再定義したり、新しいプロパティーを追加したりできるため、高度なチューニングが可能になります。
auto 設定は、レプリケートされたキャッシュ、およびローカルキャッシュに以下のプロパティーを追加します。
プロパティー名 | value | description |
---|---|---|
default.directory_provider | Filesystem | ファイルシステムベースのインデックス。詳細は、Hibernate Search のドキュメント を参照してください。 |
default.exclusive_index_use | true | 排他モードでのインデックス化操作が可能で、Hibernate Search による書き込みの最適化が可能になります。 |
default.indexmanager | Near Real Time | Lucene の near real ti me機能を利用します。つまり、インデックス化されたオブジェクトは検索にすぐに利用できます。 |
default.reader.strategy | shared | 複数のクエリーでインデックスリーダーを再利用するため、再度開くのを防ぎます。 |
分散キャッシュの場合、auto-config は Data Grid 自体のインデックスを設定し、インデックス化操作がインデックスに書き込む単一のノードに送信されるマスター/スレーブメカニズムとして内部的に処理されます。
分散キャッシュの自動設定プロパティーは次のとおりです。
プロパティー名 | value | description |
---|---|---|
default.directory_provider | infinispan | Data Grid に保存されているインデックス。詳細は、Hibernate Search のドキュメント を参照してください。 |
default.exclusive_index_use | true | 排他モードでのインデックス化操作が可能で、Hibernate Search による書き込みの最適化が可能になります。 |
default.indexmanager | org.infinispan.query.indexmanager.InfinispanIndexManager | Data Grid クラスターの単一ノードへのインデックス書き込みを委譲します。 |
default.reader.strategy | shared | 複数のクエリーでインデックスリーダーを再利用し、再度開くのを防ぎます。 |
14.2.2.8. 再インデックス化
場合によっては、Cache に保存されているデータから Lucene インデックスを再構築する必要がある場合があります。Analyzers はインデックスの記述方法に影響を与えるため、タイプでインデックスの定義を変更する場合や、一部の Analyzer パラメーターを変更する場合はインデックスを再構築する必要があります。また、一部のシステム管理エラーによって破棄された場合は、インデックスを再構築する必要がある場合があります。インデックスを再構築するには、MassIndexer への参照を取得して開始するには、グリッド内のすべてのデータを再処理する必要があるため、時間がかかる場合があります。
// Blocking execution SearchManager searchManager = Search.getSearchManager(cache); searchManager.getMassIndexer().start(); // Non blocking execution CompletableFuture<Void> future = searchManager.getMassIndexer().startAsync();
これは、org.infinispan:type=Query,manager="{name-of-cache-manager}",cache="{name-of-cache}",component=MassIndexer
配下に登録されている MassIndexer MBean で start
JMX 操作としても利用できます。
14.2.2.9. マッピングエンティティー
Data Grid は、エンティティーレベルでインデックス作成の詳細な設定を定義するため Hibernate Search の API に依存します。この設定には、アノテーションが付けられたフィールド、使用するアナライザー、ネストされたオブジェクトのマッピング方法などが含まれます。詳細なドキュメントは the Hibernate Search manual を参照してください。
14.2.2.9.1. @DocumentId
Hibernate Search とは異なり、@DocumentId を使用してフィールドを識別子としてマーク付けすると、Data Grid は値を保存するために使用されるキーになります。すべての @Indexed オブジェクトの識別子は、値を保存するために使用されるキーになります。@Transformable、カスタム型、およびカスタム FieldBridge 実装の組み合わせを使用して、キーのインデックス化方法をカスタマイズできます。
14.2.2.9.2. @Transformable keys
各値のキーはインデックス化する必要があり、キーインスタンスを String で変換する必要があります。Data Grid には、共通のプリミティブをエンコードするためのデフォルトの変換ルーチンが含まれていますが、カスタムキーを使用するには org.infinispan.query.Transformer の実装を提供する必要があります。
アノテーションを使用したキートランスフォーマーの登録
キークラスに org.infinispan.query.Transformable のアノテーションを付け、カスタムトランスフォーマー実装が自動的に選択されます。
@Transformable(transformer = CustomTransformer.class) public class CustomKey { ... } public class CustomTransformer implements Transformer { @Override public Object fromString(String s) { ... return new CustomKey(...); } @Override public String toString(Object customType) { CustomKey ck = (CustomKey) customType; return ... } }
キャッシュインデックス設定を介したキートランスフォーマーの登録
埋め込みおよびサーバー設定の両方で、key-transformers xml 要素を使用できます。
<replicated-cache name="test"> <indexing index="ALL" auto-config="true"> <key-transformers> <key-transformer key="com.mycompany.CustomKey" transformer="com.mycompany.CustomTransformer"/> </key-transformers> </indexing> </replicated-cache>
または、Java 設定 API (組み込みモード) を使用して同じ効果を得ることができます。
ConfigurationBuilder builder = ... builder.indexing().autoConfig(true) .addKeyTransformer(CustomKey.class, CustomTransformer.class);
実行時のプログラムによるトランスフォーマーの登録
この手法を使用して、カスタムキータイプにアノテーションを付ける必要も、キャッシュインデックス設定にトランスフォーマーを追加する必要もなく、代わりに org.infinispan.query.spi.SearchManagerImplementor.registerKeyTransformer(Class<?>, Class<? extends Transformer>) を呼び出すことで、実行時に SearchManagerImplementor に追加することができます。
org.infinispan.query.spi.SearchManagerImplementor manager = Search.getSearchManager(cache).unwrap(SearchManagerImplementor.class); manager.registerKeyTransformer(keyClass, keyTransformerClass);
10.0 以降、このアプローチは非推奨になりました。これは、新しく起動したノードが初期状態遷移でキャッシュエントリーを受信し、必要なキートランスフォーマーがまだ登録されていないため (キャッシュが完全に起動した後にのみ登録可能)、それらをインデックス化できない可能性があるためです。他の利用可能なアプローチ (設定およびアノテーション) を使用してキートランスフォーマーを登録すると、望ましくない状況を回避できます。
14.2.2.9.3. プログラムによるマッピング
アノテーションを使用してエンティティーをインデックスにマップする代わりに、プログラムで設定することもできます。
次の例では、グリッドに格納され、クラスにアノテーションを付けなくても 2 つのプロパティーで検索可能にするオブジェクト Author をマップします。
import org.apache.lucene.search.Query; import org.hibernate.search.cfg.Environment; import org.hibernate.search.cfg.SearchMapping; import org.hibernate.search.query.dsl.QueryBuilder; import org.infinispan.Cache; import org.infinispan.configuration.cache.Configuration; import org.infinispan.configuration.cache.ConfigurationBuilder; import org.infinispan.configuration.cache.Index; import org.infinispan.manager.DefaultCacheManager; import org.infinispan.query.CacheQuery; import org.infinispan.query.Search; import org.infinispan.query.SearchManager; import java.io.IOException; import java.lang.annotation.ElementType; import java.util.Properties; SearchMapping mapping = new SearchMapping(); mapping.entity(Author.class).indexed() .property("name", ElementType.METHOD).field() .property("surname", ElementType.METHOD).field(); Properties properties = new Properties(); properties.put(Environment.MODEL_MAPPING, mapping); properties.put("hibernate.search.[other options]", "[...]"); Configuration infinispanConfiguration = new ConfigurationBuilder() .indexing().index(Index.NONE) .withProperties(properties) .build(); DefaultCacheManager cacheManager = new DefaultCacheManager(infinispanConfiguration); Cache<Long, Author> cache = cacheManager.getCache(); SearchManager sm = Search.getSearchManager(cache); Author author = new Author(1, "Manik", "Surtani"); cache.put(author.getId(), author); QueryBuilder qb = sm.buildQueryBuilderForClass(Author.class).get(); Query q = qb.keyword().onField("name").matching("Manik").createQuery(); CacheQuery cq = sm.getQuery(q, Author.class); assert cq.getResultSize() == 1;
14.2.3. API のクエリー
以下を使用して Data Grid にクエリーを実行できます。
- Lucene または Hibernate Search クエリー。Data Grid は、Lucene クエリーを生成する Hibernate Search DSL を公開します。Lucene クエリーを単一ノードで実行するか、Data Grid クラスターの複数のノードにクエリーをブロードキャストできます。
- フルテキスト拡張を含むカスタム文字列ベースのクエリー言語である Ickle クエリー。
14.2.3.1. Hibernate Search
インデックスを設定するために Hibernate Search アノテーションをサポートする以外に、他の Hibernate Search API を使用してキャッシュをクエリーすることもできます。
14.2.3.1.1. Lucene クエリーの実行
Lucene クエリーを直接実行するには、CacheQuery でこれを作成してラップするだけです。
import org.apache.lucene.search.Query; import org.infinispan.query.CacheQuery; import org.infinispan.query.Search; import org.infinispan.query.SearchManager; SearchManager searchManager = Search.getSearchManager(cache); Query query = searchManager.buildQueryBuilderForClass(Book.class).get() .keyword().wildcard().onField("description").matching("*test*").createQuery(); CacheQuery<Book> cacheQuery = searchManager.getQuery(query);
14.2.3.1.2. Hibernate Search DSL の使用
Hibernate Search DSL を使用して Lucene クエリーを作成できます。以下に例を示します。
import org.infinispan.query.Search; import org.infinispan.query.SearchManager; import org.apache.lucene.search.Query; Cache<String, Book> cache = ... SearchManager searchManager = Search.getSearchManager(cache); Query luceneQuery = searchManager .buildQueryBuilderForClass(Book.class).get() .range().onField("year").from(2005).to(2010) .createQuery(); List<Object> results = searchManager.getQuery(luceneQuery).list();
この DSL のクエリー機能の詳細は、Hibernate Search のマニュアル の関連のセクションを参照してください。
14.2.3.1.3. ファセット検索
Data Grid は、Hibernate Search FacetManager
を使用して、ファセット検索 をサポートしています。
// Cache is indexed Cache<Integer, Book> cache = ... // Obtain the Search Manager SearchManager searchManager = Search.getSearchManager(cache); // Create the query builder QueryBuilder queryBuilder = searchManager.buildQueryBuilderForClass(Book.class).get(); // Build any Lucene Query. Here it's using the DSL to do a Lucene term query on a book name Query luceneQuery = queryBuilder.keyword().wildcard().onField("name").matching("bitcoin").createQuery(); // Wrap into a cache Query CacheQuery<Book> query = searchManager.getQuery(luceneQuery); // Define the Facet characteristics FacetingRequest request = queryBuilder.facet() .name("year_facet") .onField("year") .discrete() .orderedBy(FacetSortOrder.COUNT_ASC) .createFacetingRequest(); // Associated the FacetRequest with the query FacetManager facetManager = query.getFacetManager().enableFaceting(request); // Obtain the facets List<Facet> facetList = facetManager.getFacets("year_facet");
上記のファセット検索は、年ごとにリリースされた 'bitcoin' に一致する数字ブックを返します。以下に例を示します。
AbstractFacet{facetingName='year_facet', fieldName='year', value='2008', count=1} AbstractFacet{facetingName='year_facet', fieldName='year', value='2009', count=1} AbstractFacet{facetingName='year_facet', fieldName='year', value='2010', count=1} AbstractFacet{facetingName='year_facet', fieldName='year', value='2011', count=1} AbstractFacet{facetingName='year_facet', fieldName='year', value='2012', count=1} AbstractFacet{facetingName='year_facet', fieldName='year', value='2016', count=1} AbstractFacet{facetingName='year_facet', fieldName='year', value='2015', count=2} AbstractFacet{facetingName='year_facet', fieldName='year', value='2013', count=3}
ファセット検索の詳細は、Hibernate Search Faceting を参照してください。
14.2.3.1.4. 空間クエリー
Data Grid は Spatial Queries もサポートしているため、完全テキストを距離、地理、地理コーディネートに基づく制限と組み合わせることができます。
この例では、@Latitude
および @Longitude
とともに検索されるエンティティーで @Spatial
アノテーションを使用することから開始します。
@Indexed @Spatial public class Restaurant { @Latitude private Double latitude; @Longitude private Double longitude; @Field(store = Store.YES) String name; // Getters, Setters and other members omitted }
空間クエリーを実行するには、Hibernate Search DSL を使用できます。
// Cache is configured as indexed Cache<String, Restaurant> cache = ... // Obtain the SearchManager Searchmanager searchManager = Search.getSearchManager(cache); // Build the Lucene Spatial Query Query query = Search.getSearchManager(cache).buildQueryBuilderForClass(Restaurant.class).get() .spatial() .within( 2, Unit.KM ) .ofLatitude( centerLatitude ) .andLongitude( centerLongitude ) .createQuery(); // Wrap in a cache Query CacheQuery<Restaurant> cacheQuery = searchManager.getQuery(query); List<Restaurant> nearBy = cacheQuery.list();
詳細については、Hibernate Search のマニュアル を参照してください。
14.2.3.1.5. IndexedQueryMode
インデックス化されたクエリーのクエリーモードを指定できます。IndexedQueryMode.BROADCAST を使用すると、クラスターの各ノードにクエリーをブロードキャストし、結果を取得してから呼び出し元に戻すことができます。各ノードのローカルインデックスには、インデックス化されたデータのサブセットのみがあるため、共有されていないインデックス と併用する場合に適しています。
IndexedQueryMode.FETCH は呼び出し元でクエリーを実行します。クラスター全体のデータのすべてのインデックスがローカルで利用可能な場合、パフォーマンスが最適になります。それ以外の場合は、このクエリーモードにはリモートノードからインデックスデータの取得が含まれる場合があります。
IndexedQueryMode は、Ickle クエリーおよび Lucene クエリーでサポートされます(Query DSL ではサポートされません)。
以下に例を示します。
CacheQuery<Person> broadcastQuery = Search.getSearchManager(cache).getQuery(new MatchAllDocsQuery(), IndexedQueryMode.BROADCAST); List<Person> result = broadcastQuery.list();
14.2.3.2. Data Grid Query DSL
Query DSL (QueryBuilder および関連インターフェイス)は非推奨となり、次のメジャーバージョンで削除されます。代わりに Ickle クエリーを使用してください。
Data Grid は、Lucene および Hibernate Search に依存しない独自のクエリー DSL を提供します。基礎となるクエリーおよびインデックスメカニズムからクエリー API を切り離すと、Lucene だけでなく、同じ統一されたクエリー API を使用できるように、今後も新しい代替エンジンを導入できます。インデックスと検索の現在の実装は Hibernate Search および Lucene をベースとしているため、この章で説明するすべてのインデックス関連側面は引き続き適用されます。
新しい API は、Lucene クエリーオブジェクトを構築する低レベルの詳細にユーザーを公開せず、リモートの Hot Rod クライアントで使用できるという利点があります。ただし、詳細を分解する前に、最初に前の例から Book エンティティーのクエリーを書き込む簡単な例を見てみましょう。
Data Grid のクエリー DSL を使用したクエリーの例
import org.infinispan.query.dsl.*; // get the DSL query factory from the cache, to be used for constructing the Query object: QueryFactory qf = org.infinispan.query.Search.getQueryFactory(cache); // create a query for all the books that have a title which contains "engine": org.infinispan.query.dsl.Query query = qf.from(Book.class) .having("title").like("%engine%") .build(); // get the results: List<Book> list = query.list();
API は org.infinispan.query.dsl パッケージにあります。クエリーは、キャッシュごとの SearchManager から取得した QueryFactory インスタンスを使用して作成されます。各 QueryFactory インスタンスは SearchManager と同じ Cache インスタンスにバインドされますが、それ以外の場合は、複数のクエリーを並行して作成するために使用できるステートレスおよびスレッドセーフオブジェクトになります。
クエリーの作成は from(Class entityType)
メソッドの呼び出しで始まります。このメソッドは、指定のキャッシュから特定されたエンティティークラスにターゲットとするクエリーを作成する QueryBuilder オブジェクトを返します。
クエリーは常に単一のエンティティータイプをターゲットにし、単一のキャッシュの内容に対して評価されます。複数のキャッシュでクエリーを実行したり、複数のエンティティータイプ (結合) を対象とするクエリーを作成したりすることは、サポートされていません。
QueryBuilder は、DSL メソッドの呼び出しによって指定された検索条件や設定を蓄積し、最終的に QueryBuilder.build()
メソッドの呼び出しによって Query オブジェクトをビルドし、構築を完了させます。ステートフルオブジェクトであるため、(ネスト されたクエリーを除く) 複数のクエリーを同時に作成することはできませんが、後で再利用できます。
この QueryBuilder は Hibernate Search とは異なりますが、同様の目的があるため、同じ名前になります。あいまいさを防ぐために、まもなく名前を変更することを検討しています。
クエリーの実行と結果のフェッチは、Queryオブジェクトの list()
メソッドを呼び出すのと同じくらい簡単です。実行すると、Query オブジェクトは再利用できません。新しい結果を取得するためにこれを再実行する必要がある場合は、QueryBuilder.build()
を呼び出して新しいインスタンスを取得する必要があります。
14.2.3.2.1. Operator のフィルターリング
クエリーの構築は、複数の条件を設定する階層的なプロセスで、この階層に従って説明するのが最適です。
クエリー条件の最も単純な形式は、ゼロ以上の引数を受け入れるフィルター Operator に応じて、エンティティー属性の値を制限することです。エンティティー属性は、利用可能なすべての Operator を公開する中間コンテキストオブジェクト(FilterConditionEndContext)を返すクエリービルダーの having(String attributePath)
メソッドを呼び出して指定されます。FilterConditionEndContext で定義された各メソッドは、2 つの引数を持つ between
と引数を持たない isNull
を除いて、引数を受け取る演算子です。引数はクエリーの作成時に静的に評価されるため、SQL の相関サブクエリーと同様の機能を探している場合は、現在利用できません。
// a single query criterion QueryBuilder qb = ... qb.having("title").eq("Hibernate Search in Action");
フィルター | 引数 | 説明 |
---|---|---|
in | コレクション値 | 左のオペランドが引数として指定された値のコレクションからの要素のいずれかと等しいことを確認します。 |
in | オブジェクト値 | 左側のオペランドが、値として指定された値のリスト (固定) のいずれかと同じであることを確認します。 |
contains | オブジェクト値 | 左側の引数 (配列またはコレクションとして想定される) に指定の要素が含まれていることを確認します。 |
containsAll | コレクション値 | 左側の引数 (配列またはコレクションとして想定される) に、指定されたコレクションのすべての要素を任意の順序で含まれていることを確認します。 |
containsAll | オブジェクト値 | 左側の引数 (配列またはコレクションとして想定される) に、指定したすべての要素を任意の順序で含まれていることを確認します。 |
containsAny | コレクション値 | 左側の引数 (配列またはコレクションとして想定される) に、指定されたコレクションの要素が含まれていることを確認します。 |
containsAny | オブジェクト値 | 左側の引数 (配列またはコレクションとして想定される) に指定の要素が含まれていることを確認します。 |
isNull | 左側の引数が null であることを確認します。 | |
like | 文字列のパターン | (文字列として想定される) 左側の引数が、JPA ルールに準拠するワイルドカードパターンと一致することを確認します。 |
eq | オブジェクト値 | 左側の引数が指定の値と同じであることを確認します。 |
equal | オブジェクト値 | eq のエイリアス。 |
gt | オブジェクト値 | 左側の引数が指定の値よりも大きいことを確認します。 |
gte | オブジェクト値 | 左側の引数が指定の値以上であることを確認します。 |
lt | オブジェクト値 | 左側の引数が指定の値未満であることを確認します。 |
lte | オブジェクト値 | 左側の引数が指定の値以下であることを確認します。 |
between | Object from、Object to | 左側の引数が指定された範囲の制限の間にあることを確認します。 |
クエリ構築には、適切な順序で行わなければならない、正確に 1 回 で完了しなければならない、2 回行うとエラーになる、といったメソッド呼び出しの多段階の連鎖が必要であることに注意することが重要です。以下の例は無効であり、各ケースによって基準が無視される (あいまいなケース) または例外が出力されます (より深刻なもの)。
// Incomplete construction. This query does not have any filter on "title" attribute yet, // although the author may have intended to add one. QueryBuilder qb1 = ... qb1.having("title"); Query q1 = qb1.build(); // consequently, this query matches all Book instances regardless of title! // Duplicated completion. This results in an exception at run-time. // Maybe the author intended to connect two conditions with a boolean operator, // but this does NOT actually happen here. QueryBuilder qb2 = ... qb2.having("title").like("%Data Grid%"); qb2.having("description").like("%clustering%"); // will throw java.lang.IllegalStateException: Sentence already started. Cannot use 'having(..)' again. Query q2 = qb2.build();
14.2.3.2.2. 埋め込みエンティティーの属性に基づくフィルタリング
having
メソッドは、埋め込みエンティティー 属性を参照するためのドット区切りの属性パスも受け付けますので、以下は有効なクエリーとなります。
// match all books that have an author named "Manik" Query query = queryFactory.from(Book.class) .having("author.name").eq("Manik") .build();
属性パスの各部分は、対応するエンティティーまたは埋め込みエンティティークラスの既存のインデックス付き属性を参照する必要があります。複数のレベルの埋め込みが可能です。
14.2.3.2.3. ブール値の条件
以下の例では、複数の属性条件を論理結合 (and
) および非結合 (or
) 演算子と組み合わせて、より複雑な条件を作成する方法を示しています。ブール値演算子のよく知られている Operator の優先順位ルールはここで適用されるため、構築中に DSL メソッド呼び出しの順序は無関係です。ここで、or
が最初に呼び出された場合でも、and
Operator の優先順位は or
よりも高くなります。
// match all books that have "Data Grid" in their title // or have an author named "Manik" and their description contains "clustering" Query query = queryFactory.from(Book.class) .having("title").like("%Data Grid%") .or().having("author.name").eq("Manik") .and().having("description").like("%clustering%") .build();
ブール値の否定は、論理演算子の中で最も優先され、次の単純な属性条件にのみ適用される not
演算子で実現されます。
// match all books that do not have "Data Grid" in their title and are authored by "Manik" Query query = queryFactory.from(Book.class) .not().having("title").like("%Data Grid%") .and().having("author.name").eq("Manik") .build();
14.2.3.2.4. ネストされた条件
論理演算子の優先順位の変更は、ネストされたフィルター条件で行います。論理演算子を使用すると、以前示される 2 つの単純な属性条件を接続できますが、同じクエリーファクトリーで作成された後続の複雑な条件で単純な属性条件を接続することもできます。
// match all books that have an author named "Manik" and their title contains // "Data Grid" or their description contains "clustering" Query query = queryFactory.from(Book.class) .having("author.name").eq("Manik") .and(queryFactory.having("title").like("%Data Grid%") .or().having("description").like("%clustering%")) .build();
14.2.3.2.5. プロジェクション
一部のユースケースでは、属性のごく一部のみがアプリケーションによって実際に使用されている場合、特にドメインエンティティーにエンティティーが埋め込まれている場合、ドメインオブジェクト全体を返すのはやり過ぎです。クエリー言語を使用すると、プロジェクションを返す属性 (または属性パス) のサブセットを指定できます。展開が使用される場合、Query.list()
はドメインエンティティー全体を返しませんが、Object[] の List (プロジェクト化された属性に対応する配列) を返します。
// match all books that have "Data Grid" in their title or description // and return only their title and publication year Query query = queryFactory.from(Book.class) .select("title", "publicationYear") .having("title").like("%Data Grid%") .or().having("description").like("%Data Grid%")) .build();
14.2.3.2.6. ソート
1 つ以上の属性または属性パスに基づいて結果の順序は、属性パスとソート方向を受け入れる QueryBuilder.orderBy( )
メソッドで行われます。複数の並べ替え基準を指定すると、orderBy
メソッドの呼び出し順序が優先順位を決定します。ただし、複数の並べ替え基準は、各属性の個別のソート操作のシーケンスではなく、指定された属性のタプルで動作します。
// match all books that have "Data Grid" in their title or description // and return them sorted by the publication year and title Query query = queryFactory.from(Book.class) .orderBy("publicationYear", SortOrder.DESC) .orderBy("title", SortOrder.ASC) .having("title").like("%Data Grid%") .or().having("description").like("%Data Grid%")) .build();
14.2.3.2.7. ページネーション
QueryBuilder の maxResults プロパティーを設定することにより、返される結果の数を制限できます。これは、結果セットのページネーションを実現するために startOffset の設定と併用できます。
// match all books that have "clustering" in their title // sorted by publication year and title // and return 3'rd page of 10 results Query query = queryFactory.from(Book.class) .orderBy("publicationYear", SortOrder.DESC) .orderBy("title", SortOrder.ASC) .startOffset(20) .maxResults(10) .having("title").like("%clustering%") .build();
フェッチされる結果が maxResults に制限されている場合でも、Query.getResultSize()
を呼び出すことで、一致する結果の合計数を見つけることができます。
14.2.3.2.8. グループ化およびアグリゲーション
Data Grid には、グループ化フィールドのセットに従ってクエリー結果をグループ化し、各グループに分類される値のセットに集計関数を適用することにより、各グループからの結果の集計を構築する機能があります。グループ化および集計は、プロジェクションクエリーにのみ適用できます。サポートされる集約は avg、sum、count、max、min です。グループ化フィールドのセットは groupBy (field) メソッドで指定され、複数回呼び出すことができます。グループフィールドの定義に使用される順序は関係ありません。プロジェクションで選択されたすべてのフィールドは、グループ化フィールドであるか、以下で説明するグループ化関数の 1 つを使用して集約される必要があります。Projection フィールドは集約され、同時にグループ化に使用できます。グループ化フィールドのみを選択し、集計フィールドは選択しないクエリーは有効です。
例: ブックマークは作成者別にグループ化し、それらをカウントします。
Query query = queryFactory.from(Book.class) .select(Expression.property("author"), Expression.count("title")) .having("title").like("%engine%") .groupBy("author") .build();
選択したすべてのフィールドに集計関数が適用され、グループ化にフィールドが使用されないプロジェクションクエリーが許可されます。この場合、集計は、単一のグローバルグループが存在するかのようにグローバルに計算されます。
14.2.3.2.9. 集約
avg、sum、count、max、min の集約関数をフィールドに適用できます。
- avg() - 一連の数字の平均を計算します。許可される値は、java.lang.Number のプリミティブ番号およびインスタンスです。結果は java.lang.Double で表されます。null 以外の値がない場合、結果は代わりに null になります。
- count()- null 以外の行の数をカウントし、java.lang.Long を返します。null 以外の値がない場合、結果は代わりに 0 になります。
- max() - 見つかった最も大きな値を返します。許可される値は java.lang.Comparable のインスタンスである必要があります。null 以外の値がない場合、結果は代わりに null になります。
- min() - 見つかった最小値を返します。許可される値は java.lang.Comparable のインスタンスである必要があります。null 以外の値がない場合、結果は代わりに null になります。
- sum() - 数字のセットの合計を計算します。null 以外の値がない場合、結果は代わりに null になります。以下の表は、指定のフィールドに基づいて返されるタイプを示しています。
フィールドタイプ | 戻り値のタイプ |
---|---|
Integral (BigInteger 以外) | Long |
Float または Double | double |
BigInteger | BigInteger |
BigDecimal | BigDecimal |
14.2.3.2.10. グループ化および集計を使用したクエリーの評価
集計クエリーには、通常のクエリーのようにフィルター条件を含めることができます。フィルターリングは、グループ化操作の前後の 2 つのステージで実行できます。グループ化操作の実行前に groupBy() メソッドを起動する前に定義されたフィルター条件はすべて、キャッシュエントリーに直接 (最終的な展開ではなく) キャッシュエントリーに適用されます。これらのフィルター条件は、照会されたエンティティータイプの任意のフィールドを参照し、グループ化ステージの入力となるデータセットを制限することを目的としています。groupBy メソッドの呼び出し後に定義されたフィルター条件はすべて、展開およびグループ化操作の結果が展開されます。このフィルター条件は、groupBy フィールドまたは集約されたフィールドのいずれかを参照できます。select 句で指定されていない集約フィールドを参照することは許可されています。ただし、非集計フィールドと非グループ化フィールドを参照することは禁止されています。このフェーズでフィルターリングすると、プロパティーに基づいてグループの数が減ります。通常のクエリーと同様にソートを指定することもできます。順序付け操作は、グループ化操作後に実行され、groupBy フィールドまたは集約されたフィールドのいずれかを参照できます。
14.2.3.2.11. 名前付きクエリーパラメーターの使用
実行ごとに新しい Query オブジェクトを作成する代わりに、実行前に実際の値に置き換えることができる名前付きパラメーターをクエリーに含めることができます。これにより、クエリーを 1 度定義し、複数回効率的に実行できます。パラメーターは、Operator の右側でのみ使用でき、通常の定数値ではなく、org.infinispan.query.dsl.Expression.param(String paramName) メソッドによって生成されたオブジェクトを Operator に提供することで、クエリーの作成時に定義されます。パラメーターが定義されたら、以下の例に示すように Query.setParameter(parameterName, value) または Query.setParameters(parameterMap) のいずれかを呼び出すことで設定できます。
import org.infinispan.query.Search; import org.infinispan.query.dsl.*; [...] QueryFactory queryFactory = Search.getQueryFactory(cache); // Defining a query to search for various authors and publication years Query query = queryFactory.from(Book.class) .select("title") .having("author").eq(Expression.param("authorName")) .and() .having("publicationYear").eq(Expression.param("publicationYear")) .build(); // Set actual parameter values query.setParameter("authorName", "Doe"); query.setParameter("publicationYear", 2010); // Execute the query List<Book> found = query.list();
または、実際のパラメーター値のマップを指定して、複数のパラメーターを一度に設定することもできます。
複数の名前付きパラメーターを一度に設定する
import java.util.Map; import java.util.HashMap; [...] Map<String, Object> parameterMap = new HashMap<>(); parameterMap.put("authorName", "Doe"); parameterMap.put("publicationYear", 2010); query.setParameters(parameterMap);
クエリーの解析、検証、および実行計画の作業の大部分は、パラメーターでのクエリーの最初の実行時に実行されます。この作業は後続の実行時には繰り返し行われないため、クエリーパラメーターではなく定数値を使用した同様のクエリーの場合よりもパフォーマンスが向上します。
14.2.3.2.12. その他のクエリー DSL サンプル
Query DSL API の使用を調べる最善の方法は、テストスイートを確認することです。QueryDslConditionsTest は簡単な例です。
14.2.3.3. Ickle
Ickle クエリー言語を使用して、ライブラリーおよびリモートクライアント/サーバーモードの両方でリレーショナルおよびフルテキストクエリーを作成します。
Ickle は文字列ベースであり、以下の特徴があります。
- Java クラスをクエリーし、Protocol Buffers をサポートします。
- クエリーは単一のエンティティータイプをターゲットにすることができます。
- クエリーは、コレクションを含む埋め込みオブジェクトのプロパティーをフィルタリングできます。
- プロジェクション、集計、ソート、名前付きパラメーターをサポートします。
- インデックス付きおよびインデックスなしの実行をサポートします。
- 複雑なブール式をサポートします。
- フルテキストクエリーをサポートします。
-
user.age > sqrt(user.shoeSize+3)
などの式の計算をサポートしません。 - 結合をサポートしません。
- サブクエリーをサポートしません。
- さまざまな Data Grid API でサポートされています。Query が受け入れるたびに、継続的なクエリーやリスナーのイベントフィルターに QueryBuilder が生成されます。
API を使用するには、まず QueryFactory をキャッシュに取得してから、.create() メソッドを呼び出し、クエリーで使用する文字列を渡します。たとえば、以下のようになります。
QueryFactory qf = Search.getQueryFactory(remoteCache); Query q = qf.create("from sample_bank_account.Transaction where amount > 20");
Ickle を使用する場合、フルテキスト演算子で使用されるすべてのフィールドは、Indexed
および Analysed
の両方である必要があります。
14.2.3.3.1. Ickle クエリー言語パーサー構文
Ickle クエリー言語のパーサー構文には、いくつかの重要なルールがあります。
- 空白は重要ではありません。
- フィールド名ではワイルドカードはサポートされません。
- デフォルトのフィールドがないため、フィールド名またはパスは必ず指定する必要があります。
-
&&
および||
は、フルテキストと JPA 述語の両方で、AND
またはOR
の代わりに使用できます。 -
!
はNOT
の代わりに使用できます。 -
足りないブール値 Operator は
OR
として解釈されます。 - 文字列の用語は、一重引用符または二重引用符で囲む必要があります。
- ファジー性とブースティングは任意の順序で受け入れられず、常にファジー性が最初になります。
-
<>
の代わりに!=
が許可されます。 -
ブーディングは、
>
、>=
、<
、⇐
Operator には適用できません。同じ結果を達成するために範囲を使用することができます。
14.2.3.3.2. Fuzzy クエリー
ファジークエリー add ~
を整数とともに実行するには、用語の後に使用される用語からの距離を表します。たとえば、以下のようになります。
Query fuzzyQuery = qf.create("from sample_bank_account.Transaction where description : 'cofee'~2");
14.2.3.3.3. 範囲クエリー
以下の例に示すように、範囲クエリーを実行するには、中括弧のペア内で指定の境界を定義します。
Query rangeQuery = qf.create("from sample_bank_account.Transaction where amount : [20 to 50]");
14.2.3.3.4. フレーズクエリー
次の例に示すように、単語のグループは引用符で囲むことで検索できます。
Query q = qf.create("from sample_bank_account.Transaction where description : 'bus fare'");
14.2.3.3.5. 近接クエリー
特定の距離内で 2 つの用語を検索して近接クエリーを実行するには、フレーズの後に距離とともに ~
を追加します。たとえば、以下の例では、キャンセル と fee という単語が 3 個以上ありません。
Query proximityQuery = qf.create("from sample_bank_account.Transaction where description : 'canceling fee'~3 ");
14.2.3.3.6. ワイルドカードクエリー
単一文字およびマルチ文字のワイルドカード検索の両方を実行できます。
- 単一文字のワイルドカード検索は ? 文字で使用できます。
- マルチ文字のワイルドカード検索は * 文字で使用できます。
テキストを検索するか、テストするには、以下の単一文字のワイルドカード検索を使用します。
Query wildcardQuery = qf.create("from sample_bank_account.Transaction where description : 'te?t'");
test、tests、または tester を検索するには、以下のマルチ文字のワイルドカード検索を使用します。
Query wildcardQuery = qf.create("from sample_bank_account.Transaction where description : 'test*'");
14.2.3.3.7. 正規表現クエリー
正規表現クエリーは、/ 間のパターンを指定することで実行できます。Ickle は Lucene の正規表現構文を使用しているため、単語 moat または boat を検索するには、以下を使用できます。
Query regExpQuery = qf.create("from sample_library.Book where title : /[mb]oat/");
14.2.3.3.8. クエリーのブースト
用語は、指定のクエリーにおける耐障害性を高めるために ^ を追加し、条件を強化できます。たとえば、ビールとビールとの関連性が 3 倍高いビールとワインを含むタイトルを検索するには、次のように使用できます。
Query boostedQuery = qf.create("from sample_library.Book where title : beer^3 OR wine");
14.2.3.4. 継続的なクエリー
継続的なクエリーにより、アプリケーションはクエリーフィルターに現在一致したエントリーを受信するリスナーを登録し、さらにキャッシュ操作の結果としてクエリーされたデータセットへの変更を継続的に通知できます。これには、セットに結合された値の着信一致、更新された一致、変更されて引き続き一致する一致値、およびセットを離れた値の発信一致が含まれます。継続的なクエリーを使用することにより、アプリケーションは、変更を検出するために同じクエリーを繰り返し実行する代わりに、イベントの安定したストリームを受信し、リソースがより効率的に使用されるようになります。たとえば、以下のユースケースすべてで、継続的なクエリーを使用できます。
- Person エンティティーに age プロパティーがあり、ユーザーアプリケーションによって更新される 18〜25 歳の人を返す。
- $2000 を超えるすべてのトランザクションを返す。
- F1 レーサーのラップスピードが 1:45.00 秒未満だったすべての時間を返す (キャッシュにラップエントリーが含まれていて、レース中にラップがライブ入力されていると仮定)。
14.2.3.4.1. 連続クエリー実行
継続的クエリーは、以下の場合に通知されるリスナーを使用します。
- エントリーは、Join イベントによって表される指定のクエリーの一致を開始します。
- 一致するエントリーが更新され、Update event によって表されるクエリーの一致が継続されます。
- エントリーは、Leave イベントで表されるクエリーの一致を停止します。
クライアントが継続的なクエリーリスナーを登録すると、すぐにクエリーに一致する結果の受信を開始します (上記のように Join イベントとして受信された)。さらに、通常は作成、変更、削除、または有効期限イベントを生成するキャッシュ操作の結果として、他のエントリーが Join イベントとしてクエリーの一致を開始したとき、または Leave イベントとしてクエリーの一致を停止したときに、後続の通知を受信します。更新されたキャッシュエントリーは、操作の前後でエントリーがクエリーフィルターに一致する場合、Update イベントを生成します。要約すると、リスナーが Join、Update、または Leave イベントを受信するかどうかを決定するために使用されるロジックは次のとおりです。
- 古い値と新しい値の両方に対するクエリーが false と評価された場合、イベントは抑制されます。
- 古い値に対するクエリーが false と評価され、新しい値に対するクエリーが true と評価された場合、Join イベントが送信されます。
- 古い値と新しい値の両方のクエリーが true と評価されると、Update イベントが送信されます。
- 古い値に対するクエリーが true と評価され、新しい値に対するクエリーが false と評価された場合、Leave イベントが送信されます。
- 古い値に対するクエリーが true と評価され、エントリーが削除または期限切れになると、Leave イベントが送信されます。
継続的なクエリーは、グループ化、集計、およびソート操作を除き、Query DSL の完全な機能を使用できます。
14.2.3.4.2. 継続的なクエリーの実行
継続的なクエリーを作成するには、最初に Query オブジェクトを作成して開始します。これは、クエリー DSL セクション で説明されています。次に、キャッシュの ContinuousQuery (org.infinispan.query.api.continuous.ContinuousQuery) オブジェクトを取得し、クエリーと継続的なクエリーリスナー (org.infinispan.query.api.continuous.ContinuousQueryListener) を登録する必要があります。キャッシュに関連付けられた ContinuousQuery オブジェクトは、リモートモードで実行されている場合は静的メソッド org.infinispan.client.hotrod.Search.getContinuousQuery(RemoteCache<K, V> cache) を、組み込みモードで実行されている場合は org.infinispan.query.Search.getContinuousQuery(Cache<K, V> cache) を呼び出して取得することができます。リスナーを作成したら、ContinuousQuery の addContinuousQueryListener メソッドを使用して登録できます。
continuousQuery.addContinuousQueryListener(query, listener);
以下の例は、埋め込みモードの単純な継続的なクエリーのユースケースを示しています。
継続的クエリーの登録
import org.infinispan.query.api.continuous.ContinuousQuery; import org.infinispan.query.api.continuous.ContinuousQueryListener; import org.infinispan.query.Search; import org.infinispan.query.dsl.QueryFactory; import org.infinispan.query.dsl.Query; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; [...] // We have a cache of Persons Cache<Integer, Person> cache = ... // We begin by creating a ContinuousQuery instance on the cache ContinuousQuery<Integer, Person> continuousQuery = Search.getContinuousQuery(cache); // Define our query. In this case we will be looking for any Person instances under 21 years of age. QueryFactory queryFactory = Search.getQueryFactory(cache); Query query = queryFactory.from(Person.class) .having("age").lt(21) .build(); final Map<Integer, Person> matches = new ConcurrentHashMap<Integer, Person>(); // Define the ContinuousQueryListener ContinuousQueryListener<Integer, Person> listener = new ContinuousQueryListener<Integer, Person>() { @Override public void resultJoining(Integer key, Person value) { matches.put(key, value); } @Override public void resultUpdated(Integer key, Person value) { // we do not process this event } @Override public void resultLeaving(Integer key) { matches.remove(key); } }; // Add the listener and the query continuousQuery.addContinuousQueryListener(query, listener); [...] // Remove the listener to stop receiving notifications continuousQuery.removeContinuousQueryListener(listener);
21 歳未満の Person インスタンスがキャッシュに追加されると、リスナーによって受信され、 matches マップに配置されます。これらのエントリーがキャッシュから削除されるか、年齢が 21 歳以上に変更されると、それらは matches から削除されます。
14.2.3.4.3. 継続的なクエリーの削除
クエリーのそれ以上の実行を停止するには、リスナーを単に削除します。
continuousQuery.removeContinuousQueryListener(listener);
14.2.3.4.4. 継続的なクエリーのパフォーマンスに関する注意
継続的なクエリーは、アプリケーションに一定の更新ストリームを提供するように設計されており、特に幅広いクエリーに対して非常に多くのイベントが生成される可能性があります。イベントごとに新規の一時的なメモリー割り当てが行われます。この動作によりメモリーが不足し、クエリーが適切に設計されていない場合、OutOfMemoryErrors(特にリモートモード) が発生する可能性があります。このような問題を防ぐために、一致するエントリーの数と各一致のサイズの両方の観点から、各クエリーが必要な最小限の情報をキャプチャーし (プロジェクションを使用して興味深いプロパティーをキャプチャできます)、各 ContinuousQueryListener が受信したすべてのイベントをブロックせずにすばやく処理し、リッスンするキャッシュから一致する新しいイベントの生成につながるアクションの実行を回避するように設計されていることが強く推奨されます。