Elasticsearch RestClient 入門

📢 この記事は gemini-2.5-flash によって翻訳されました

Elasticsearch シリーズ

内容リンク
Elasticsearch 基本操作https://blog.yexca.net/archives/226
Elasticsearch クエリ操作https://blog.yexca.net/archives/227
RestClient 基本操作本文
RestClient クエリ操作https://blog.yexca.net/archives/229
Elasticsearch データ集計https://blog.yexca.net/archives/231
Elasticsearch オートコンプリートhttps://blog.yexca.net/archives/232
Elasticsearch データ同期https://blog.yexca.net/archives/234
Elasticsearch クラスターhttps://blog.yexca.net/archives/235

ESの公式は、ESを操作するための様々な言語のクライアントを提供しているんだ。これらのクライアントの核は、DSLステートメントを組み立てて、HTTPリクエストを通じてESに送信することだよ。

公式ドキュメント: https://www.elastic.co/guide/en/elasticsearch/client/index.html

以下では、Java HighLevel Rest Client のクライアントAPIを使うよ。

インデックスの作成

データベースのテーブル構造はこんな感じ。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
`id` bigint(20) NOT NULL COMMENT 'ホテルID',
`name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT 'ホテル名',
`address` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT 'ホテル住所',
`price` int(10) NOT NULL COMMENT 'ホテル価格',
`score` int(2) NOT NULL COMMENT 'ホテル評価',
`brand` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT 'ホテルブランド',
`city` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '所在都市',
`star_name` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT 'ホテル星評価、1つ星から5つ星、1つダイヤモンドから5つダイヤモンド',
`business` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT 'ビジネスエリア',
`latitude` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '緯度',
`longitude` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '経度',
`pic` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT 'ホテル画像',
PRIMARY KEY (`id`) USING BTREE

インデックス作成で一番大事なのはマッピングだよ。考慮すべき点は以下だね。

  • フィールド名、フィールドデータ型(データベーステーブル構造の名前と型を参考にしてね)
  • 検索に参加させるか(ビジネス要件に基づいて判断する。例えば、画像URLは検索不要だよね)
  • 分かち書きが必要か(内容による。例えば、都市は分かち書き不要だよ)
  • 分かち書きアナライザーは何か(ik_max_wordで統一してもいいよ)

上記の表の例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
# インデックスを作成
PUT /hotel
{
  "mappings": {
    "properties": {
      "id": {
        "type": "keyword"
      },
      "name": {
        "type": "text",
        "analyzer": "ik_max_word",
        "copy_to": "all"
        /* 現在のフィールドを指定されたフィールド "all" にコピーする */
      },
      "address": {
        "type": "keyword",
        "index": false
      },
      "price": {
        "type": "integer"
      },
      "score": {
        "type": "integer"
      },
      "brand": {
        "type": "keyword",
        "copy_to": "all"
      },
      "city": {
        "type": "keyword",
        "copy_to": "all"
      },
      "starName": {
        "type": "keyword"
      },
      "business": {
        "type": "keyword"
      },
      "location": {
        "type": "geo_point"
        /* ESは2種類の地理座標データ型をサポートしているよ */
      },
      "pic": {
        "type": "keyword",
        "index": false
      },
      "all": {
        /* 結合フィールド。クエリ結果には表示されないけど、クエリには使えるよ */
        "type": "text",
        "analyzer": "ik_max_word"
      }
    }
  }
}

地理座標データ型:

  • geo_point:緯度 (latitude) と経度 (longitude) で決定される点のこと。例えば "32.84 120.25" だね。
  • geo_shape:複数の geo_point で構成される複雑な幾何学図形のこと。例えば直線 "LINESTRING(-77.03 38.29, +77.00 38.88)" みたいにね。

RestClient の初期化

Elasticsearchとのすべてのインタラクションは、RestHighLevelClientというクラスにカプセル化されているよ。

まず、ESのRestHighLevelClientの依存関係を導入するよ。

1
2
3
4
<dependency>
    <groupId>org.elasticsearch.client</groupId>
    <artifactId>elasticsearch-rest-high-level-client</artifactId>
</dependency>

SpringBootのデフォルトESバージョンは7.6.2だから、デフォルトのESバージョンを上書きする必要があるんだ。

1
2
3
4
<properties>
    <java.version>1.8</java.version>
    <elasticsearch.version>7.12.1</elasticsearch.version>
</properties>

RestHighLevelClientの初期化コードはこんな感じ。

1
2
3
RestHighLevelClient client = new RestHighLevelClient(RestClient.builder(
        HttpHost.create("http://IP:9200")
));

でも、テストの便宜のために、初期化コードは@BeforeEachに置くよ。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
public class HotelIndexTest {
    private RestHighLevelClient client;

    @BeforeEach
    void setUp() {
        client = new RestHighLevelClient(RestClient.builder(
                        HttpHost.create("http://127.0.0.1:9200")));
    }

    @AfterEach
    void tearDown() throws IOException {
        this.client.close();
    }
}

インデックスを定義するにはDSLステートメントのJSON部分が必要だから、これは別途抽出できるよ。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
public class HotelContants {
    public static final String MAPPING_TEMPLATE = "{\n" +
            "  \"mappings\": {\n" +
            "    \"properties\": {\n" +
            "      \"id\": {\n" +
            "        \"type\": \"keyword\"\n" +
            "      },\n" +
            "      \"name\": {\n" +
            "        \"type\": \"text\",\n" +
            "        \"analyzer\": \"ik_max_word\",\n" +
            "        \"copy_to\": \"all\"\n" +
            "      },\n" +
            "      \"address\": {\n" +
            "        \"type\": \"keyword\",\n" +
            "        \"index\": false\n" +
            "      },\n" +
            "      \"price\": {\n" +
            "        \"type\": \"integer\"\n" +
            "      },\n" +
            "      \"score\": {\n" +
            "        \"type\": \"integer\"\n" +
            "      },\n" +
            "      \"brand\": {\n" +
            "        \"type\": \"keyword\",\n" +
            "        \"copy_to\": \"all\"\n" +
            "      },\n" +
            "      \"city\": {\n" +
            "        \"type\": \"keyword\",\n" +
            "        \"copy_to\": \"all\"\n" +
            "      },\n" +
            "      \"starName\": {\n" +
            "        \"type\": \"keyword\"\n" +
            "      },\n" +
            "      \"business\": {\n" +
            "        \"type\": \"keyword\"\n" +
            "      },\n" +
            "      \"location\": {\n" +
            "        \"type\": \"geo_point\"\n" +
            "      },\n" +
            "      \"pic\": {\n" +
            "        \"type\": \"keyword\",\n" +
            "        \"index\": false\n" +
            "      },\n" +
            "      \"all\": {\n" +
            "        \"type\": \"text\",\n" +
            "        \"analyzer\": \"ik_max_word\"\n" +
            "      }\n" +
            "    }\n" +
            "  }\n" +
            "}";
}

インデックス作成コード

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// 注意CreateIndexRequestのパッケージ
import org.elasticsearch.client.indices.CreateIndexRequest;

@Test
void testCreateHotelIndex() throws IOException {
    // Requestオブジェクトを作成
    CreateIndexRequest request = new CreateIndexRequest("hotel");
    // リクエストパラメータ
    request.source(MAPPING_TEMPLATE, XContentType.JSON);
    // リクエストを送信
    client.indices().create(request, RequestOptions.DEFAULT);
}

インデックスの削除

DSLステートメント

1
DELETE /hotel

作成コードとの違いは、Requestオブジェクトだけ。パラメータもないんだ。

1
2
3
4
5
6
7
@Test
void testDeleteHotelIndex() throws IOException {
    // リクエストを作成
    DeleteIndexRequest request = new DeleteIndexRequest("hotel");
    // リクエストを送信
    client.indices().delete(request, RequestOptions.DEFAULT);
}

インデックスの存在確認

DSLステートメント

1
GET /hotel

違いはやっぱりRequestオブジェクトだよ。

1
2
3
4
5
6
7
8
9
@Test
void testExistsHotelIndex() throws IOException {
    // リクエストを作成
    GetIndexRequest request = new GetIndexRequest("hotel");
    // リクエストを送信
    boolean exists = client.indices().exists(request, RequestOptions.DEFAULT);
    // 出力
    System.out.println(exists);
}

まとめ

JavaRestClientでESを操作する流れは基本的に似てるよ。client.indices() メソッドを使ってインデックス操作オブジェクトを取得するんだ。

RestClient

通常、データはデータベースで、データベースクエリを通じてインデックスのCRUD操作を行うよ。

RestHighLevelClient の初期化

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
@SpringBootTest
public class HotelDocumentTest {
    // サービスを注入
    @Autowired
    private IHotelService hotelService;

    private RestHighLevelClient client;

    @BeforeEach
    void setUp() {
        // RestClient を構築
        this.client = new RestHighLevelClient(
                RestClient.builder(
                        HttpHost.create("http://127.0.0.1:9200")));
    }

    @AfterEach
    void tearDown() throws IOException {
        this.client.close();
    }
}

ドキュメントの追加

データベースからデータをクエリして、ESに書き込むよ。

構造の調整

データベースクエリの結果はHotel型のオブジェクトだよ。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
@Data
@TableName("tb_hotel")
public class Hotel {
    @TableId(type = IdType.INPUT)
    private Long id;
    private String name;
    private String address;
    private Integer price;
    private Integer score;
    private String brand;
    private String city;
    private String starName;
    private String business;
    private String longitude;
    private String latitude;
    private String pic;
}

インデックスの構造とは違うから、インデックスと同じ構造の新しい型を定義する必要があるんだ。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@Data
@NoArgsConstructor
public class HotelDoc {
    private Long id;
    private String name;
    private String address;
    private Integer price;
    private Integer score;
    private String brand;
    private String city;
    private String starName;
    private String business;
    private String location;
    private String pic;

    public HotelDoc(Hotel hotel) {
        this.id = hotel.getId();
        this.name = hotel.getName();
        this.address = hotel.getAddress();
        this.price = hotel.getPrice();
        this.score = hotel.getScore();
        this.brand = hotel.getBrand();
        this.city = hotel.getCity();
        this.starName = hotel.getStarName();
        this.business = hotel.getBusiness();
        this.location = hotel.getLatitude() + ", " + hotel.getLongitude();
        this.pic = hotel.getPic();
    }
}

追加構文

DSLの構文はこれ。

1
2
3
4
5
POST /{indexName}/_doc/{id}
{
    "name": "Jack",
    "age": 21
}

対応するJavaコード

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
@Test
public void HotelCreateTest() throws IOException {
    // requestオブジェクトを準備
    IndexRequest request = new IndexRequest("indexName").id("1");
    // JSONオブジェクトを準備
    request.source("{\n" +
            "    \"name\": \"Jack\",\n" +
            "    \"age\": 21\n" +
            "}", XContentType.JSON);
    // リクエストを送信
    client.index(request, RequestOptions.DEFAULT);
}

ドキュメント追加のサンプルコード

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
@Test
public void HotelCreateTest() throws IOException {
    // ホテルデータをクエリ
    Hotel hotel = hotelService.getById(61083L);
    // ドキュメント型に変換
    HotelDoc hotelDoc = new HotelDoc(hotel);
    // JSONに変換
    String json = JSON.toJSONString(hotelDoc);

    // requestオブジェクトを準備
    IndexRequest request = new IndexRequest("hotel").id(hotelDoc.getId().toString());
    // JSONオブジェクトを準備
    request.source(json, XContentType.JSON);
    // リクエストを送信
    client.index(request, RequestOptions.DEFAULT);
}

ドキュメントの検索

検索構文

DSLステートメント

1
GET /{indexName}/_doc/{id}

Javaステートメント

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
@Test
public void HotelGetTest() throws IOException {
    // requestを準備
    GetRequest request = new GetRequest("indexName", "id");
    // リクエストを送信
    GetResponse response = client.get(request, RequestOptions.DEFAULT);
    // 結果を解析
    String json = response.getSourceAsString();
    
    System.out.println(json);
}

ドキュメント検索のサンプルコード

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
@Test
public void HotelGetTest() throws IOException {
    // requestを準備
    GetRequest request = new GetRequest("hotel", "61083");
    // リクエストを送信
    GetResponse response = client.get(request, RequestOptions.DEFAULT);
    // 結果を解析
    String json = response.getSourceAsString();
    // Javaオブジェクトに逆シリアル化
    HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);
    System.out.println(hotelDoc);
}

ドキュメントの削除

削除構文

DSLステートメント

1
DELETE /{indexName}/_doc/{id}

Javaステートメント

1
2
3
4
5
6
7
@Test
public void HotelDeleteTest() throws IOException {
    // requestを準備
    DeleteRequest request = new DeleteRequest("indexName", "id");
    // リクエストを送信
    client.delete(request, RequestOptions.DEFAULT);
}

ドキュメント削除のサンプルコード

1
2
3
4
5
6
7
@Test
public void HotelDeleteTest() throws IOException {
    // requestを準備
    DeleteRequest request = new DeleteRequest("hotel", "61083");
    // リクエストを送信
    client.delete(request, RequestOptions.DEFAULT);
}

ドキュメントの変更

変更構文

変更には全量変更と増分変更があるんだけど、RestClientのAPIでは、この2つの方法は完全に同じAPIを使うんだ。判断基準はIDで、新規追加の場合は:

  • IDが存在する場合、変更
  • IDが存在しない場合、新規追加

ここでは主に増分変更に注目するね。DSLステートメントはこれ。

1
2
3
4
5
POST /{indexName}/_update/{id}
{
    "doc": "Rose",
    "age": 18
}

Javaステートメント

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
@Test
public void HotelUpdateTest() throws IOException {
    // requestオブジェクトを作成
    UpdateRequest request = new UpdateRequest("indexName", "id");
    // パラメータを準備
    request.doc(
            "name", "Rose",
            "age", 18
    );
    // ドキュメントを更新
    client.update(request, RequestOptions.DEFAULT);
}

ドキュメント変更のサンプルコード

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
@Test
public void HotelUpdateTest() throws IOException {
    // requestオブジェクトを作成
    UpdateRequest request = new UpdateRequest("hotel", "61083");
    // パラメータを準備
    request.doc(
            "price", 950,
            "starName", "四钻"
    );
    // ドキュメントを更新
    client.update(request, RequestOptions.DEFAULT);
}

ドキュメントの一括インポート

BulkRequestを使って、データベースのデータをインデックスに一括インポートするよ。これは、複数の通常のCRUDリクエストをまとめて送信するっていうのが本質なんだ。例はこんな感じ。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
@Test
public void testBulk() throws IOException {
    // bulkリクエストを作成
    BulkRequest request = new BulkRequest();
    // bulkリクエストを追加
    request.add(new IndexRequest("indexName").id("id1").source("json1", XContentType.JSON));
    request.add(new IndexRequest("indexName").id("id2").source("json2", XContentType.JSON));
    // bulkリクエストを送信
    client.bulk(request, RequestOptions.DEFAULT);
    }

ドキュメント一括インポートのサンプルコード

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
@Test
public void testBulk() throws IOException {
    // データを一括クエリ
    List<Hotel> hotelList = hotelService.list();

    // bulkリクエストを作成
    BulkRequest request = new BulkRequest();
    // bulkリクエストを追加
    for (Hotel hotel : hotelList) {
        // ドキュメント型に変換
        HotelDoc hotelDoc = new HotelDoc(hotel);
        // ドキュメント新規追加requestオブジェクトを作成
        request.add(new IndexRequest("hotel")
                .id(hotelDoc.getId().toString())
                .source(JSON.toJSONString(hotelDoc), XContentType.JSON)
        );
    }

    // bulkリクエストを送信
    client.bulk(request, RequestOptions.DEFAULT);
}

Visits Since 2025-02-28

Hugo で構築されています。 | テーマ StackJimmy によって設計されています。