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

# 準備
```xml: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のライブラリが使えるようになります。

# 基本的なライブラリの説明と使い方
<font color="red">※以降に出てくるカラム名やフィールド名やIDなどはすべて実在するものではありません。</font>

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

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

```java:java
SearchRequest searchRequest = new SearchRequest();
```

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

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

```java: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: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）を記載でき、それぞれの条件でのスコアを合算した値を使ってソートを行います。

```java:あくまで一例
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: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: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: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を使ってソートを指定します。

```java:複数カラムでソートの場合
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: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: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)
    )
)
```

```json: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: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で叩いてみるなどしてクエリが正しいものか確認しましょう。

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

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