1. 序章

Elasticsearch を使用したことがある人なら誰でも、RESTful検索APIを使用してクエリを作成するのは面倒でエラーが発生しやすいことを知っています。

このチュートリアルでは、Elasticsearch用のHTTPJavaクライアントであるJestについて説明します。 Elasticsearchは独自のネイティブJavaクライアントを提供しますが、Jestはより流暢なAPIとを操作するためのより簡単なインターフェースを提供します。

2. Mavenの依存関係

最初に行う必要があるのは、JestライブラリをPOMにインポートすることです。

<dependency>
    <groupId>io.searchbox</groupId>
    <artifactId>jest</artifactId>
    <version>6.3.1</version>
</dependency>

Jestのバージョン管理は、Elasticsearchのメイン製品のバージョン管理に従います。 これにより、クライアントとサーバー間の互換性が確保されます。

Jest依存関係を含めることにより、対応するElasticsearchライブラリが推移的な依存関係として含まれます。

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. インデックスの管理

インデックスが存在するかどうかを確認するには、IndicatorsExistsアクションを使用します。

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

また、エイリアスの作成または変更も、ModifiedAliasesアクションを使用して簡単に行えます。

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文字列を手動で作成することはできますが、適切なフォーマット、中括弧、および引用文字のエスケープに注意する必要があります。

したがって、 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メソッドの1つを呼び出す必要があります。 結果を生のJSONとして取得するか、DTOに逆シリアル化することができます。

Employee getResult = jestClient.execute(new Get.Builder("employees", "1").build())
    .getSourceAsObject(Employee.class);

ドキュメントにアクセスするもう1つの方法は、Searchアクションを使用してJestに実装されている検索クエリを使用することです。

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つの欠点は、アプリケーションが完全なElasticsearchライブラリに依存する必要があることです。

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クライアントはこのオブジェクトを呼び出します。 インターフェイスには、completefailedの2つのメソッドがあり、それぞれ操作の成功または失敗を処理できます。

6. 結論

このチュートリアルでは、Elasticsearch用のRESTfulJavaクライアントであるJestクライアントについて簡単に説明しました。

その機能のごく一部しかカバーしていませんが、Jestが堅牢なElasticsearchクライアントであることは明らかです。 その流暢なビルダークラスとRESTfulインターフェースにより、習得が容易になり、Elasticsearchインターフェースの完全なサポートにより、ネイティブクライアントの有能な代替手段になります。

いつものように、チュートリアルのすべてのコード例は、GitHub上にあります。