Jest – Elasticsearch Javaクライアント

1. 前書き

link:/elasticsearch-java[Elasticsearch]で作業したことがある人は、https://www.baeldung.com/elasticsearch-full-text-search-rest-api [RESTful検索API]は退屈でエラーが発生しやすくなります。
このチュートリアルでは、ElasticsearchのHTTP Javaクライアントであるhttps://github.com/searchbox-io/Jest[Jest]を取り上げます。 Elasticsearchは独自のネイティブJavaクライアントを提供しますが、* Jestはより流fluentなAPIと使いやすいインターフェイスを提供します*。

2. メーベン依存

最初に行う必要があるのは、https://search.maven.org/search?q = g:io.searchbox%20a:jest [Jest library]をPOMにインポートすることです。
<dependency>
    <groupId>io.searchbox</groupId>
    <artifactId>jest</artifactId>
    <version>6.3.1</version>
</dependency>
  • Jestのバージョン管理は、主要なElasticsearch製品のバージョン管理に従います*。 これにより、クライアントとサーバー間の互換性を確保できます。

    Jest依存関係を含めることにより、対応するhttps://search.maven.org/search?q=g:org.elasticsearch%20a:elasticsearch[Elasticsearch library]が推移的な依存関係として含まれます。

3. Jestクライアントを使用する

このセクションでは、Jestクライアントを使用してElasticsearchで一般的なタスクを実行する方法について説明します。
Jestクライアントを使用するには、_JestClientFactory_を使用して_JestClient_オブジェクトを作成するだけです。 *これらのオブジェクトは作成に費用がかかり、スレッドセーフ*です。そのため、アプリケーション全体で共有できるシングルトンインスタンスを作成します。
public JestClient jestClient() {
    JestClientFactory factory = new JestClientFactory();
    factory.setHttpClientConfig(
      new HttpClientConfig.Builder("http://localhost:9200")
        .multiThreaded(true)
        .defaultMaxTotalConnectionPerRoute(2)
        .maxTotalConnection(10)
        .build());
    return factory.getObject();
}
これにより、ローカルで実行されているElasticsearchクライアントに接続されたJestクライアントが作成されます。 この接続例は簡単ですが、* Jestはプロキシ、SSL、認証、さらにはノード検出さえも完全にサポートしています*。
_JestClient_クラスはジェネリックであり、パブリックメソッドはほんの一握りです。 *使用する主なものは_execute_ *であり、_Action_インターフェイスのインスタンスを受け取ります。 Jestクライアントは、Elasticsearchと対話するさまざまなアクションの作成に役立ついくつかのビルダークラスを提供します。
*すべてのJest呼び出しの結果は、_JestResult_ *のインスタンスです。 _isSucceeded_を呼び出すことで成功を確認できます。 アクションが失敗した場合は、_getErrorMessage_を呼び出して詳細を取得できます。
JestResult jestResult = jestClient.execute(new Delete.Builder("1").index("employees").build());

if (jestResult.isSucceeded()) {
    System.out.println("Success!");
}
else {
    System.out.println("Error: " + jestResult.getErrorMessage());
}

3.1. インデックスの管理

インデックスが存在するかどうかを確認するには、_IndicesExists_アクションを使用します。
JestResult result = jestClient.execute(new IndicesExists.Builder("employees").build())
インデックスを作成するには、_CreateIndex_アクションを使用します。
jestClient.execute(new CreateIndex.Builder("employees").build());
これにより、デフォルト設定でインデックスが作成されます。 インデックスの作成中に特定の設定をオーバーライドできます。
Map<String, Object> settings = new HashMap<>();
settings.put("number_of_shards", 11);
settings.put("number_of_replicas", 2);
jestClient.execute(new CreateIndex.Builder("employees").settings(settings).build());
また、_ModifyAliases_アクションを使用すると、エイリアスの作成または変更も簡単です。
jestClient.execute(new ModifyAliases.Builder(
  new AddAliasMapping.Builder("employees", "e").build()).build());
jestClient.execute(new ModifyAliases.Builder(
  new RemoveAliasMapping.Builder("employees", "e").build()).build());

3.2. ドキュメントを作成する

Jestクライアントを使用すると、_Index_アクションクラスを使用して新しいドキュメントのインデックス作成または作成を簡単に行うことができます。 * Elasticsearchのドキュメントは単なるJSONデータ*であり、JSONデータをインデックス作成のためにJestクライアントに渡す方法は複数あります。
この例では、架空のEmployeeドキュメントを使用してみましょう。
{
    "name": "Michael Pratt",
    "title": "Java Developer",
    "skills": ["java", "spring", "elasticsearch"],
    "yearsOfService": 2
}
JSONドキュメントを表す最初の方法は、Java _String_を使用することです。 JSON文字列を手動で作成できますが、適切な書式設定、中括弧、引用文字のエスケープに注意する必要があります。
したがって、https://www.baeldung.com/jackson-object-mapper-tutorial [Jackson]などのJSONライブラリを使用してJSON構造を構築し、_String_に変換する方が簡単です。
ObjectMapper mapper = new ObjectMapper();
JsonNode employeeJsonNode = mapper.createObjectNode()
  .put("name", "Michael Pratt")
  .put("title", "Java Developer")
  .put("yearsOfService", 2)
  .set("skills", mapper.createArrayNode()
    .add("java")
    .add("spring")
    .add("elasticsearch"));
jestClient.execute(new Index.Builder(employeeJsonNode.toString()).index("employees").build());
Java _Map_を使用してJSONデータを表し、それを_Index_アクションに渡すこともできます。
Map<String, Object> employeeHashMap = new LinkedHashMap<>();
employeeHashMap.put("name", "Michael Pratt");
employeeHashMap.put("title", "Java Developer");
employeeHashMap.put("yearsOfService", 2);
employeeHashMap.put("skills", Arrays.asList("java", "spring", "elasticsearch"));
jestClient.execute(new Index.Builder(employeeHashMap).index("employees").build());
最後に、Jestクライアントは、インデックスを作成するドキュメントを表すPOJOを受け入れることができます。 _Employee_クラスがあるとしましょう:
public class Employee {
    String name;
    String title;
    List<String> skills;
    int yearsOfService;
}
このクラスのインスタンスを_Index_ビルダーに直接渡すことができます。
Employee employee = new Employee();
employee.setName("Michael Pratt");
employee.setTitle("Java Developer");
employee.setYearsOfService(2);
employee.setSkills(Arrays.asList("java", "spring", "elasticsearch"));
jestClient.execute(new Index.Builder(employee).index("employees").build());

3.3. 文書を読む

Jestクライアントを使用してElasticsearchからドキュメントにアクセスするには、主に2つの方法があります。 まず、ドキュメントIDがわかっている場合は、_Get_アクションを使用して直接アクセスできます。
jestClient.execute(new Get.Builder("employees", "17").build());
返されたドキュメントにアクセスするには、さまざまな_getSource_メソッドのいずれかを呼び出す必要があります。 結果を生のJSONとして取得するか、DTOにデシリアライズして戻すことができます。
Employee getResult = jestClient.execute(new Get.Builder("employees", "1").build())
    .getSourceAsObject(Employee.class);
ドキュメントにアクセスするもう1つの方法は、Jestで_Search_アクションを使用して実装される検索クエリを使用することです。
  • Jestクライアントは、完全なElasticsearchクエリDSL *をサポートしています。 インデックス作成操作と同様に、クエリはJSONドキュメントとして表され、検索を実行する方法は複数あります。

    最初に、検索クエリを表すJSON文字列を渡すことができます。 念のため、文字列が適切にエスケープされ、有効なJSONであることを確認する必要があります。
String search = "{" +
  "  \"query\": {" +
  "    \"bool\": {" +
  "      \"must\": [" +
  "        { \"match\": { \"name\":   \"Michael Pratt\" }}" +
  "      ]" +
  "    }" +
  "  }" +
  "}";
jestClient.execute(new Search.Builder(search).build());
上記の_Index_アクションと同様に、Jacksonなどのライブラリを使用してJSONクエリ文字列を作成できます。
さらに、ネイティブのElasticsearchクエリアクションAPIを使用することもできます。 この欠点の1つは、アプリケーションが完全なhttps://search.maven.org/search?q=g:org.elasticsearch%20a:elasticsearch[Elasticsearch library]に依存する必要があることです。
_Search_アクションでは、_getSource_メソッドを使用して、一致するドキュメントにアクセスできます。 ただし、* Jestは、一致するドキュメントをラップし、結果に関するメタデータを提供する_Hit_クラスも提供します*。 _Hit_クラスを使用して、各結果の追加のメタデータにアクセスできます:スコア、ルーティング、および結果の説明など、いくつか例を挙げます。
List<SearchResult.Hit<Employee, Void>> searchResults =
  jestClient.execute(new Search.Builder(search).build())
    .getHits(Employee.class);
searchResults.forEach(hit -> {
    System.out.println(String.format("Document %s has score %s", hit.id, hit.score));
});

3.4. ドキュメントの更新

Jestは、ドキュメントを更新するための簡単な_Update_アクションを提供します。
employee.setYearOfService(3);
jestClient.execute(new Update.Builder(employee).index("employees").id("1").build());
前に見た_Index_アクションと同じJSON表現を受け入れ、2つの操作間でコードを簡単に共有できます。

3.5. ドキュメントを削除する

インデックスからドキュメントを削除するには、_Delete_アクションを使用します。 インデックス名とドキュメントIDのみが必要です。
jestClient.execute(new Delete.Builder("17")
  .index("employees")
  .build());

4. 一括操作

  • Jestクライアントは一括操作もサポートしています。*これは、複数の操作を同時に送信することで時間と帯域幅を節約できることを意味します。

    _Bulk_アクションを使用して、任意の数の要求を1つの呼び出しに結合できます。 さまざまなタイプのリクエストを組み合わせることができます。
jestClient.execute(new Bulk.Builder()
  .defaultIndex("employees")
  .addAction(new Index.Builder(employeeObject1).build())
  .addAction(new Index.Builder(employeeObject2).build())
  .addAction(new Delete.Builder("17").build())
  .build());

5. 非同期操作

  • Jestクライアントは非同期操作もサポートしています*。つまり、非ブロッキングI / Oを使用して上記の操作を実行できます。

    操作を非同期的に呼び出すには、クライアントの_executeAsync_メソッドを使用するだけです:
jestClient.executeAsync(
  new Index.Builder(employeeObject1).build(),
  new JestResultHandler<JestResult>() {
      @Override public void completed(JestResult result) {
          // handle result
      }
      @Override public void failed(Exception ex) {
          // handle exception
      }
  });
アクション(この場合はインデックス付け)に加えて、非同期フローには_JestResultHandler._も必要であることに注意してください。Jestクライアントは、アクションが終了するとこのオブジェクトを呼び出します。 インターフェイスには、_completed_および_failed_の2つのメソッドがあり、それぞれ操作の成功または失敗を処理できます。

6. 結論

このチュートリアルでは、ElasticsearchのRESTful JavaクライアントであるJestクライアントについて簡単に説明しました。
機能のごく一部しか取り上げていませんが、Jestが堅牢なElasticsearchクライアントであることは明らかです。 流fluentなビルダークラスとRESTfulインターフェイスにより学習が容易になり、Elasticsearchインターフェイスの完全なサポートにより、ネイティブクライアントに代わる有能な代替手段となります。
いつものように、チュートリアルのコード例はすべてhttps://github.com/eugenp/tutorials/tree/master/persistence-modules/elasticsearch[over on GitHub]です。