Elasticsearch APIをJavaで使ってみた


きっかけ

Elasticsearch APIをJavaで使って実装するにあたって、
基本的な使い方はQiitaにもあったり公式リファレンスにもありますが、
ちょっと複雑化したり、より実践的な記述やコードが記載されている記事が少ないので、本記事を執筆することにしました。

この記事の対象となる方

  • Elasticsearch APIをJavaで使って開発をこれから行いたい・行う予定の方
  • Elasticsearchビギナーの方

環境(開発当時)

  • Elasticsearch APIバージョン:7.3.2
  • Javaバージョン:8
  • Spring Bootバージョン:1.5.15
  • mavenバージョン:2.17

準備

pom.xml
<dependency>
    <groupId>org.elasticsearch</groupId>
    <artifactId>elasticsearch</artifactId>
    <version>7.3.2</version>
</dependency>

<dependency>
    <groupId>org.elasticsearch.client</groupId>
    <artifactId>elasticsearch-rest-high-level-client</artifactId>
    <version>7.3.2</version>
</dependency>

上記を記載するとElasticsearchのライブラリが使えるようになります。

基本的なライブラリの説明と使い方

※以降に出てくるカラム名やフィールド名やIDなどはすべて実在するものではありません。

SearchRequest

リファレンスの英語を翻訳にかけると

SearchRequestは、ドキュメント、集約、サジェストを検索する操作に使用され、結果として得られるドキュメントのハイライト表示を要求する方法も提供します。

とありますが、APIを使ってElasticsearchに対してリクエストを送るための大本となるものみたいな感じだと思います。

java
SearchRequest searchRequest = new SearchRequest();

参考:https://www.elastic.co/guide/en/elasticsearch/client/java-rest/master/java-rest-high-search.html

SearchSourceBuilder

SearchSourceBuilder は検索パラメータを追加するためのものです。

java
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();

参考:https://www.elastic.co/guide/en/elasticsearch/client/java-rest/master/java-rest-high-search.html

RestHighLevelClient

RestHighLevelClientはそれまで利用されていたTransportClientに代わって推奨されている、RESTクライアント。
使用することで、Javaアプリからhttpを介してElasticsearchへアクセスできます。

java
final CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
credentialsProvider.setCredentials(AuthScope.ANY,
    new UsernamePasswordCredentials("user", "password")
);

RestHighLevelClient client = new RestHighLevelClient(
    RestClient.builder(new HttpHost("localhost", 9200, "https"))
        .setHttpClientConfigCallback(httpAsyncClientBuilder -> httpAsyncClientBuilder
          .setDefaultCredentialsProvider(credentialsProvider))
);

searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);

参考:https://www.elastic.co/guide/en/elasticsearch/client/java-rest/current/_basic_authentication.html

function score query

function score queryは、functionsセクションに複数の条件(function)を記載でき、それぞれの条件でのスコアを合算した値を使ってソートを行います。

あくまで一例
FunctionScoreQueryBuilder functionScoreQueryBuilder = null;
ArrayList<FunctionScoreQueryBuilder.FilterFunctionBuilder> functionScoreArrayList = new ArrayList<>();
// 複数addしてもOK
filterFunctionList.add(
    new FunctionScoreQueryBuilder.FilterFunctionBuilder(QueryBuilders.termQuery("FashionItemId", "1"),
        ScoreFunctionBuilders.fieldValueFactorFunction("custom_score.").factor(Float.valueOf("0.0254389"
)).missing(0.2)));
// ArrayList型をFunctionScoreQueryBuilder.FilterFunctionBuilder[]にする
FunctionScoreQueryBuilder.FilterFunctionBuilder[] functions = functionScoreArrayList.toArray(new FunctionScoreQueryBuilder.FilterFunctionBuilder[functionScoreArrayList.size()]);
functionScoreQueryBuilder = new FunctionScoreQueryBuilder(queryBuilder, functions).scoreMode(FunctionScoreQuery.ScoreMode.SUM).boostMode(CombineFunction.REPLACE);
searchSourceBuilder.query(functionScoreQueryBuilder);

※FunctionScoreQueryBuilderは本当に参考になる記事が少ないです・・・。

参考:https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-function-score-query.html

count

RestHighLevelClientに対して .count で指定できます。

java
CountResponse countResponse = null;
countResponse = restHighLevelClient.count(searchRequest, RequestOptions.DEFAULT);

とするとsearchRequestにヒットした件数をCountResponseの形式で取得することができます。

参考:https://www.elastic.co/guide/en/elasticsearch/reference/current/search-count.html

search

RestHighLevelClientに対して .search で指定できます。

java
SearchResponse searchResponse = null;
searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);

とするとsearchRequestにヒットした検索結果をSearchResponseの形式で取得することができます。

参考:https://www.elastic.co/guide/en/elasticsearch/reference/current/search-search.html

_source

Elasticsearchの _source はSQLでいうSELECT句みたいなものです。
APIの場合は取得するフィールドをfetchSourceを使って指定します。
取得するフィールドを絞り込むことでデータ量の削減にもつながるので、速度の改善が期待できます。
第一引数には取得するフィールド、第二引数に除外するフィールドを指定します。
前述したcountの場合では指定なしで大丈夫です。

java
searchSourceBuilder.fetchSource(new String[]{"FashionItemId", "ItemPrice", "FashionItemSize",
    "FashionItemLargeCategory", "FashionItemSmallCategory"},
    "ExclusionFashionItemId");

参考:https://www.elastic.co/guide/en/elasticsearch/client/java-rest/current/java-rest-high-search.html#_source_filtering

Sort

SQLでいうOrder Byです。
FieldSortBuilderを使ってソートを指定します。

複数カラムでソートの場合
return searchSourceBuilder.sort(new FieldSortBuilder("ItemPrice").order(SortOrder.ASC))
    .sort(new FieldSortBuilder("FashionItemId").order(SortOrder.DESC))
    .sort(new FieldSortBuilder("StartDatetime").order(SortOrder.DESC));

参考:https://www.elastic.co/guide/en/elasticsearch/client/java-rest/current/java-rest-high-search.html#_specifying_sorting

from & size

SQLでいうoffset & limitに該当するものです。
前述したSearchSourceBuilderに対して .from .to で指定できます。

java
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchSourceBuilder.from(0);
searchSourceBuilder.size(100);

参考:https://www.elastic.co/guide/en/elasticsearch/client/java-rest/master/java-rest-high-search.html#_using_the_searchsourcebuilder

BoolQuery

ほかのクエリ同士を組み合わせるために使います。
AND, OR, NOTを組みあわせる事ができます。
BoolQueryには4種類あります。

クエリ 説明
must SQLでいうANDです。指定された条件によってスコアが計算されます。
filter SQLでいうANDです。mustと違いスコアが計算されません。
should SQLでいうORです。
must not SQLでいうNOTです。

代表的な検索クエリ

termQuery

合致するかどうか。SQLでいう=(イコール)
参考:https://www.elastic.co/guide/en/elasticsearch/reference/7.8/query-dsl-term-query.html

temrsQuery

合致するものがあるかどうか。SQLでいうIN句
参考:https://www.elastic.co/guide/en/elasticsearch/client/java-api/current/java-term-level-queries.html#java-query-dsl-terms-query

rangeQuery

指定した範囲のものがあるか。SQLでいう >=や<=、<、>のこと
参考:https://www.elastic.co/guide/en/elasticsearch/client/java-api/current/java-term-level-queries.html#java-query-dsl-range-query

例(SQL vs ElasticSearch vs Java)

SQLの場合
AND StartDatetime >= '2019-12-06 17:33:18'
AND (
    (
        FashionItemLargeCategory <> 1
        AND FashionItemSmallCategory NOT IN (10,20,30)
        AND FashionItemSize IN (1,2)
    ) OR (
        (
            FashionItemLargeCategory = 2
            OR FashionItemSmallCategory  IN (40,50,60)
        )
        AND FashionItemSize  IN (9,10)
    )
)
Elasticsearchの場合
"bool": {
  "filter": [
    {
      "range": {
        "StartDatetime": {
          "from": null,
          "to": "2019-12-06 17:33:18",
          "include_lower": true,
          "include_upper": false
        }
      }
    },
    {
      "bool": {
        "filter": [
          {
            "bool": {
              "should": [
                {
                  "bool": {
                    "filter": [
                      {
                        "terms": {
                          "FashionItemSize ": [
                            1,
                            2
                          ]
                        }
                      }
                    ],
                    "must_not": [
                      {
                        "term": {
                          "FashionItemLargeCategory ": {
                            "value": 1,
                            "boost": 1
                          }
                        }
                      },
                      {
                        "terms": {
                          "FashionItemSmallCategory ": [
                            10,
                            20,
                            30
                          ]
                        }
                      }
                    ]
                  }
                },
                {
                  "bool": {
                    "filter": [
                      {
                        "terms": {
                          "FashionItemSize": [
                            9,
                            10
                          ]
                        }
                      }
                    ],
                    "should": [
                        {
                          "term": {
                            "FashionItemLargeCategory ": {
                              "value": 1
                            }
                          }
                        },
                        {
                          "terms": {
                            "FashionItemSmallCategory  ": [
                              40,
                              50,
                              60
                            ]
                          }
                        }
                    ]
                  }
                }
              ]
            }
          }
        ]
      }
    }
  ]
}

(すごい複雑で見にくい・・・・。)

javaの場合
integer[] smallCategories1 = {10, 20, 30};
integer[] itemSize1 = {1, 2};
integer[] smallCategories2 = {40, 50, 60};
integer[] itemSize2 = {9, 10};

BoolQueryBuilder qb1 = boolQuery()
    .mustNot(termQuery("FashionItemLargeCategory", 1))
    .mustNot(termsQuery("FashionItemSmallCategory", smallCategories1))
    .filter(termsQuery("FashionItemSize" , itemSize1));

BoolQueryBuilder qb2 = boolQuery()
    .should(termQuery("FashionItemLargeCategory", 2))
    .should(termsQuery("FashionItemSmallCategory", smallCategories2))
    .filter(termsQuery("FashionItemSize", itemSize2));

BoolQueryBuilder qb3 = boolQuery()
    .should(qb1)
    .should(qb2);

BoolQueryBuilder qb4 = boolQuery()
    .filter(rangeQuery("StartDatetime").from(null).lt("2019-12-06 17:33:18"))
    .filter(qb3);

検証方法

BoolQueryBuilderに対してtoString()してあげることでElasticsearchのクエリを取得することができます。
その結果を想定していたクエリと照らし合わせたり、Kibanaで叩いてみるなどしてクエリが正しいものか確認しましょう。

例 (SQL vs ElasticSearch vs Java) のBoolQueryBuilderのクエリを検証する場合
System.out.println(qb4.toString());

最後に

この記事はあくまでも初めて自分がさわった当時の情報などになります。
当時は記事も少なかったものの現在では調べれば良質な記事が多くなっているかもしれません。
少しでも初めてElasticsearchAPIを触る方の参考になれば幸いです。