Elasticsearch Java High Level REST ClientでCRUD操作を行う

15 min read読了の目安(約14200字

本記事ではローカル開発環境にElasticsearchサーバを立て、公式Javaクライアント(High Level REST Client)を使ってCRUD操作を行う手順を書きます。

本記事に使ったコードは下記リポジトリでも公開しています。

https://github.com/ryo-utsunomiya/elasticsearch-java

使用しているソフトウェアのバージョン

環境構築

  • http://localhost:9200 でElasticsearchにアクセスできる
  • http://localhost:5601 でKibanaにアクセスできる(Elasticsearchの動作確認用)
  • CLIで動作するJavaアプリケーションで上記エンドポイントにアクセスできる

という状態を目標に構築します。

DockerでElasticsearch + Kibana

docker-compose を使ってElasticsearchとKibanaのサーバを立てます。

version: '2.2'
services:
  elasticsearch:
    image: docker.elastic.co/elasticsearch/elasticsearch:7.10.1
    environment:
      - discovery.type=single-node
      - cluster.name=es-docker-cluster
      - bootstrap.memory_lock=true
      - "ES_JAVA_OPTS=-Xms512m -Xmx512m"
    ulimits:
      memlock:
        soft: -1
        hard: -1
    volumes:
      - data01:/usr/share/elasticsearch/data
    ports:
      - 9200:9200
  kibana:
    image: docker.elastic.co/kibana/kibana:7.10.1
    ports:
      - 5601:5601

volumes:
  data01:
    driver: local

Dockerの設定については下記公式ドキュメントを参考にしています。

https://www.elastic.co/guide/en/elasticsearch/reference/current/docker.html
https://www.elastic.co/guide/en/kibana/current/docker.html
$ docker-compose up -d

でDockerコンテナを起動し、動作確認します。

Elasticsearchはcurlで動作確認。

$ curl -XGET "http://localhost:9200/"
{
  "name" : "75936f071a0a",
  "cluster_name" : "es-docker-cluster",
  "cluster_uuid" : "PXddY1qDQr-y1vkY6mBwrQ",
  "version" : {
    "number" : "7.10.1",
    "build_flavor" : "default",
    "build_type" : "docker",
    "build_hash" : "1c34507e66d7db1211f66f3513706fdf548736aa",
    "build_date" : "2020-12-05T01:00:33.671820Z",
    "build_snapshot" : false,
    "lucene_version" : "8.7.0",
    "minimum_wire_compatibility_version" : "6.8.0",
    "minimum_index_compatibility_version" : "6.0.0-beta1"
  },
  "tagline" : "You Know, for Search"
}

Kibanaは http://localhost:5601 にブラウザでアクセスします。

kibana screenshot

Javaアプリケーションの作成

次にGradleでJavaアプリケーションを作成します。

$ gradle init --type java-application \
--dsl groovy \
--test-framework junit-jupiter \
--project-name esjava \
--package esjava

こんな感じのディレクトリ構成になるはず。

$ tree
.
├── app
│   ├── build.gradle
│   └── src
│       ├── main
│       │   ├── java
│       │   │   └── esjava
│       │   │       └── App.java
│       │   └── resources
│       └── test
│           ├── java
│           │   └── esjava
│           │       └── AppTest.java
│           └── resources
├── gradle
│   └── wrapper
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── settings.gradle

12 directories, 8 files

app/build.gradle を編集して、GradleのdependenciesにElasticsearchのクライアントライブラリを追加します(gradle initで生成されたコメントは削除し、guavaも使わないのでdependenciesから削除しています)。

plugins {
    id 'application'
}

repositories {
    jcenter()
    maven { url 'https://snapshots.elastic.co/maven/' }
}

dependencies {
    testImplementation 'org.junit.jupiter:junit-jupiter-api:5.6.2'
    testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine'
    implementation 'org.elasticsearch.client:elasticsearch-rest-high-level-client:7.10.1'
}

application {
    mainClass.set('esjava.App')
}

tasks.named('test') {
    useJUnitPlatform()
}

Elasticsearchが localhost:9200 で動いている状態で、以下のJavaコードを実行することで動作確認できます。

package esjava;

import java.io.IOException;
import org.apache.http.HttpHost;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;

public class App {

  public static void main(String[] args) {
    try (var client = new RestHighLevelClient(
        RestClient.builder(new HttpHost("localhost", 9200, "http")))) {

      var request = new SearchRequest();
      var response = client.search(request, RequestOptions.DEFAULT);
      System.out.println(response.status().toString());

    } catch (IOException ex) {
      ex.printStackTrace();
    }
  }
}

アプリケーションの実行結果は以下のようになります( gradlew ではなくIDEの実行機能を使っても問題ありません)。

$ ./gradlew run

> Task :app:run
# ※Elasticsearchクライアントから警告が出るが省略
OK

BUILD SUCCESSFUL in 2s
2 actionable tasks: 1 executed, 1 up-to-date

ElasticsearchをJavaクライアントで操作する

ドキュメントの作成

curlならこんな感じで、 _doc にリクエストを送ります。POSTメソッドを使うとid指定なしで作成、PUTメソッドならid指定もできます。

$ curl -XPOST "http://localhost:9200/my_index/_doc/" \
-H 'Content-Type: application/json' \
-d '{"message":"Hello, Elasticsearch!"}'
{
  "_index" : "my_index",
  "_type" : "_doc",
  "_id" : "mWzetnYB_4LLU29fksD0",
  "_version" : 1,
  "result" : "created",
  "_shards" : {
    "total" : 2,
    "successful" : 1,
    "failed" : 0
  },
  "_seq_no" : 0,
  "_primary_term" : 1
}

これをJavaで書くとこう。Document APIs の Index APIを使います。リクエストに使うJSONの組み立て方法はいくつかありますが、ここでは request.source() にMapオブジェクトを渡しています。

package esjava;

import java.io.IOException;
import java.util.Map;
import org.apache.http.HttpHost;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;

public class App {

  public static void main(String[] args) {
    try (var client = new RestHighLevelClient(
        RestClient.builder(new HttpHost("localhost", 9200, "http")))) {

      var request = new IndexRequest("my_index");
//    request.id("1"); // 指定したidでリソースを作成 or 更新(HTTPのPUT相当)
      request.source(Map.of("message", "Hello, Elasticsearch!"));
      var response = client.index(request, RequestOptions.DEFAULT);
      System.out.printf("created: %s\n", response.getId());

    } catch (IOException ex) {
      ex.printStackTrace();
    }
  }
}

ドキュメントをid指定して取得

作成したドキュメントのidは、Elasticsearchに生成を任せている場合、 mWzetnYB_4LLU29fksD0 のようなランダムな文字列になっています。このidを指定することでドキュメントを取得できます。

$ curl -XGET "http://localhost:9200/my_index/_doc/mWzetnYB_4LLU29fksD0?pretty"
{
  "_index" : "my_index",
  "_type" : "_doc",
  "_id" : "mWzetnYB_4LLU29fksD0",
  "_version" : 1,
  "_seq_no" : 0,
  "_primary_term" : 1,
  "found" : true,
  "_source" : {
    "message" : "Hello, Elasticsearch!"
  }
}

Javaでは Document APIs の Get API を使います。

package esjava;

import java.io.IOException;
import org.apache.http.HttpHost;
import org.elasticsearch.action.get.GetRequest;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;

public class App {

  public static void main(String[] args) {
    try (var client = new RestHighLevelClient(
        RestClient.builder(new HttpHost("localhost", 9200, "http")))) {

      var request = new GetRequest("my_index", "mWzetnYB_4LLU29fksD0");
      var response = client.get(request, RequestOptions.DEFAULT);
      System.out.println(response.getSourceAsString());

    } catch (IOException ex) {
      ex.printStackTrace();
    }
  }
}

メタデータが不要で _source フィールドの値のみ取得できれば良い場合は、 _source にリクエストを投げます。

$ curl -XGET "http://localhost:9200/my_index/_source/mWzetnYB_4LLU29fksD0?pretty"
{
  "message" : "Hello, Elasticsearch!"
}

Javaの場合は Document APIs の Get Source APIを使います。

package esjava;

import java.io.IOException;
import org.apache.http.HttpHost;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.client.core.GetSourceRequest;

public class App {

  public static void main(String[] args) {
    try (var client = new RestHighLevelClient(
        RestClient.builder(new HttpHost("localhost", 9200, "http")))) {

      var request = new GetSourceRequest("my_index", "mWzetnYB_4LLU29fksD0");
      var response = client.getSource(request, RequestOptions.DEFAULT);
      System.out.println(response.toString());

    } catch (IOException ex) {
      ex.printStackTrace();
    }
  }
}

ドキュメントの検索

_search に検索クエリを送るとさまざまな検索が行えます。

$ curl -XGET "http://localhost:9200/my_index/_search" \
-H 'Content-Type: application/json' \
-d '{"query":{"match":{"message":"Elasticsearch"}}}'
# レスポンスは割愛

Javaでは Search APIs の Search API を使います。

package esjava;

import java.io.IOException;
import java.util.Arrays;
import org.apache.http.HttpHost;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.builder.SearchSourceBuilder;

public class App {

  public static void main(String[] args) {
    try (var client = new RestHighLevelClient(
        RestClient.builder(new HttpHost("localhost", 9200, "http")))) {

      var request = new SearchRequest("my_index");
      request.source(
          SearchSourceBuilder.searchSource()
              .query(QueryBuilders.matchQuery("message", "Elasticsearch")));
      var response = client.search(request, RequestOptions.DEFAULT);
      Arrays.stream(response.getHits().getHits())
          .forEach(h -> System.out.println(h.getSourceAsString()));

    } catch (IOException ex) {
      ex.printStackTrace();
    }
  }
}

ドキュメントの更新

更新は完全な置き換えと一部フィールドの書き換えでエンドポイントが異なります。

置き換えの場合は _doc にPUTリクエストを送ります。

curl -XPUT "http://elasticsearch:9200/my_index/_doc/mWzetnYB_4LLU29fksD0" \
-H 'Content-Type: application/json' \
-d '{"message":"Hello, Again"}'

Javaでは Document APIsのIndex APIを使います。

package esjava;

import java.io.IOException;
import java.util.Map;
import org.apache.http.HttpHost;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;

public class App {

  public static void main(String[] args) {
    try (var client = new RestHighLevelClient(
        RestClient.builder(new HttpHost("localhost", 9200, "http")))) {

      var request = new IndexRequest("my_index");
      request.id("mWzetnYB_4LLU29fksD0");
      request.source(Map.of("message", "Hello, Again"));
      var response = client.index(request, RequestOptions.DEFAULT);
      System.out.println(response.getResult());

    } catch (IOException ex) {
      ex.printStackTrace();
    }
  }
}

部分更新は _update にPOSTリクエストを送ります(POSTというと新規作成というイメージがありますが、ここでは「更新」というリソースを作成する、という意味だと思います)。

curl -XPOST "http://localhost:9200/my_index/_update/mWzetnYB_4LLU29fksD0" \
-H 'Content-Type: application/json' \
-d '{"doc":{"message":"Hello, Again"}}'

Javaでは Document APIs の Update APIを使います。

package esjava;

import java.io.IOException;
import java.util.Map;
import org.apache.http.HttpHost;
import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;

public class App {

  public static void main(String[] args) {
    try (var client = new RestHighLevelClient(
        RestClient.builder(new HttpHost("localhost", 9200, "http")))) {

      var request = new UpdateRequest("my_index", "mWzetnYB_4LLU29fksD0");
      request.doc(Map.of("message", "Hello, Again"));
      var response = client.update(request, RequestOptions.DEFAULT);
      System.out.println(response.getResult());

    } catch (IOException ex) {
      ex.printStackTrace();
    }
  }
}

ドキュメントを削除する

curl -XDELETE "http://localhost:9200/my_index/_doc/mWzetnYB_4LLU29fksD0"

Javaでは Documents APIs の Delete API を使います。

package esjava;

import java.io.IOException;
import org.apache.http.HttpHost;
import org.elasticsearch.action.delete.DeleteRequest;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;

public class App {

  public static void main(String[] args) {
    try (var client = new RestHighLevelClient(
        RestClient.builder(new HttpHost("localhost", 9200, "http")))) {

      var request = new DeleteRequest("my_index", "mWzetnYB_4LLU29fksD0");
      var response = client.delete(request, RequestOptions.DEFAULT);
      System.out.println(response.getResult());

    } catch (IOException ex) {
      ex.printStackTrace();
    }
  }
}

むすび

以上、Elasticsearch Java High Level REST Clientで基本的なCRUD操作を行う方法を紹介しました。High Level REST Clientでは、Document APIsを使うことでCRUD操作を行えます。

Document APIsには他にも、ドキュメントの存在確認(Exists)まとめて取得(Multi GET)など様々なAPIがあります。

Elasticsearchの本領は「検索(Search)」ですが、そちらは記事を改めてまとめたいと思います。