Java High Level REST Client Tips

[Elastic Stack (Elasticsearch) Advent Calendar 2019](https://qiita.com/advent-calendar/2019/elasticsearch)の11日目の記事です

# はじめに
「Java High Level REST Client」を使用することで、Javaアプリからhttpを介してElasticsearchへアクセスできる

以前は「[TransportClient](https://www.elastic.co/guide/en/elasticsearch/client/java-api/current/transport-client.html)」が使用されていたが8.0で廃止されているため、
「[Java High Level REST Client Tips](https://www.elastic.co/guide/en/elasticsearch/client/java-rest/master/java-rest-high.html)」 もしくは「[Java Low Level REST Client](https://www.elastic.co/guide/en/elasticsearch/client/java-rest/master/java-rest-low.html)」の使用が推奨されている

今回は、検索(Search API)に絞った使用例を紹介します

# 注意点
* Java High Level REST ClientはJava 1.8が必要
* Elasticsearchとの接続は、マイナーバージョンの通信保証する

例) 
Elasticsearch(7.0)  Java High Level REST(7.0)  → OK
Elasticsearch(7.0)  Java High Level REST(6.8)  → NG

※通信を保証するだけで、バージョンアップ時の新機能は対応してないことが多い

# 準備
Elasticsearchの環境　→ [ElasticCloud](https://www.elastic.co/jp/cloud/)
Java8(JDK1.8.0)

## インデックス「qiita」を作成

```
PUT /qiita
{
  "mappings": {
    "properties": {
      "user":    { "type": "keyword" },  
      "post_date":  { "type": "date"  }, 
      "active":  { "type": "boolean"  },
      "message":   { "type": "text"  }     
    }
  }
}

POST qiita/_doc
{
    "user" : "qiita",
    "post_date" : "2019-12-11T00:10:30Z",
    "active":"false",
    "message" : "trying out High Level REST Client"
}

GET qiita/_search
...
      {
        "_index" : "qiita",
        "_type" : "_doc",
        "_id" : "yjDo5m4Bj4TzcUq3pmoX",
        "_score" : 1.0,
        "_source" : {
          "user" : "qiita",
          "post_date" : "2019-12-11T00:10:30Z",
          "active" : "false",
          "message" : "trying out High Level REST Client"
        }
      }
...
```

## pon.xml


```
    <dependencies>
        ...
        <dependency>
            <groupId>org.elasticsearch.client</groupId>
            <artifactId>elasticsearch-rest-high-level-client</artifactId>
            <version>7.5.0</version>
        </dependency>
        <dependency>
            <groupId>org.elasticsearch.client</groupId>
            <artifactId>elasticsearch-rest-client</artifactId>
            <version>7.5.0</version>
        </dependency>
        ...
    </dependencies>

```


# 接続用Clinent作成

基本的には、[Initialization](https://www.elastic.co/guide/en/elasticsearch/client/java-rest/7.5/java-rest-high-getting-started-initialization.html)の例で問題ない


```
RestHighLevelClient client = new RestHighLevelClient(
        RestClient.builder(
                new HttpHost("localhost", 9200, "http"),
                new HttpHost("localhost", 9201, "http")));

client.close();
```

## ElasticCloudの場合
ElasticCloudなど認証機能を導入している場合は、下記の設定が必要がある

```
String username = "elastic";
String password = "pass";
String host = "host";
int port = 9243;
int nextPort = 9244;
String protocol = "https";


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

RestClientBuilder client = RestClient.builder(
                new HttpHost(host, port, protocol),
                new HttpHost(host, nextPort, protocol))
                .setHttpClientConfigCallback((h) -> h.setDefaultCredentialsProvider(credentialsProvider));

RestHighLevelClient client = new RestHighLevelClient(client);

client.close();
```

# データ取得

先ほど作成したclientを使用することで「GET qiita/_search」と似た取得ができます

```
try (RestHighLevelClient client = new RestHighLevelClient(client)) {

            SearchSourceBuilder searchBuilder = SearchSourceBuilder.searchSource();
            // quite*のようにワイルドカード指定できる
            SearchRequest request = new SearchRequest("qiita").source(searchBuilder);
            // データ取得
            SearchHits hits = client.search(request, RequestOptions.DEFAULT).getHits();
            for(SearchHit hit : hits) {
                Map<String, Object> sourceAsMap = hit.getSourceAsMap();

　　　　　　　　　 //　１レコードごとの値を設定 
                String user = (String) sourceAsMap.get("user");
                String post_date = (String) sourceAsMap.get("post_date");
                String message = (String) sourceAsMap.get("message");
    
                System.out.println(String.format("user:%s data:%s message:%s",user , post_date, message));
            }
}catch (IOException io) {}
```

参考: [Search API](https://www.elastic.co/guide/en/elasticsearch/client/java-rest/7.5/java-rest-high-search.html)

# サイズの設定
「SearchSourceBuilder」に設定する
サイズはデフォルトは10件しか取得しない

```
// default 10
SearchSourceBuilder searchBuilder = SearchSourceBuilder.searchSource();
searchBuilder.size(100);
            
```

# ソート
「SearchSourceBuilder」に設定する
SortOrderでASCとDESCを切り替える

```
SearchSourceBuilder searchBuilder = SearchSourceBuilder.searchSource();
searchBuilder.sort(new FieldSortBuilder("_id").order(SortOrder.DESC));
```

# 検索クエリ（フィルタ）
[Building Queries](https://www.elastic.co/guide/en/elasticsearch/client/java-rest/current/java-rest-high-query-builders.html)に書かれているのは一通り使える（全部試したわけではない）が簡単なものを紹介する
基本、「SearchSourceBuilder」に設定する

## Range Query(期間指定)
日本時刻の「2019-12-12T00:10:30」から「2019-12-13T00:10:31」まで取得している

```
SearchSourceBuilder searchBuilder = SearchSourceBuilder.searchSource();

QueryBuilder query = QueryBuilders
       .rangeQuery("post_date")
       .from("2019-12-12T00:10:30")
       .to("2019-12-13T00:10:31")
       .timeZone("+09:00");

searchBuilder.query(query);
```

注意が必要なのは、下記のように指定すると
「2019-12-09T00:00:00.000」〜「2019-12-13T23:59:59.999」まで取得する

```
QueryBuilder query = QueryBuilders
       .rangeQuery("post_date")
       .from("2019-12-12")
       .to("2019-12-13")
       .timeZone("+09:00");

searchBuilder.query(query);
```

12日のみのデータを取得するには下記のようになる

fromを含める場合は「includeLower」：true
toを含める場合は「includeUpper」  ：true

```
QueryBuilder query = QueryBuilders
       .rangeQuery("post_date")
       .from("2019-12-12")
       .to("2019-12-13")
　　　　.includeLower(true)
　　　　.includeUpper(false)
       .timeZone("+09:00");

searchBuilder.query(query);
```

## Match Query
全文検索のクエリは下記

```
SearchSourceBuilder searchBuilder = SearchSourceBuilder.searchSource();

QueryBuilder query = QueryBuilders.matchQuery("message", "REST Level ");
QueryBuilder query = QueryBuilders.matchQuery("message", "Level REST");
QueryBuilder query = QueryBuilders.matchPhraseQuery("message", "REST Level");
// フレーズ検索なので取得０件
QueryBuilder query = QueryBuilders.matchPhraseQuery("message", "Level REST");

searchBuilder.query(query);
```

## Term Query
完全一致で検索される、Termクエリは下記

```
QueryBuilder query = QueryBuilders.termQuery("user", "qiita");
```

## Bool Query（AND　OR　NOT）


| Elastic |      Elastic SQL        |   説明    |
|:-----------------|:------------------|:------------------|
|  must           |              AND |              |
|    filter         |              AND |        スコアを無視する       |
|       should      |              OR |               |
|    mustnot         |              NOT |               |
※スコアを使用しない検索の場合はなるべくfilterを使う
[Elasticsearchのbool queryを利用してAND OR NOTを書いてみる](http://qiita.com/vanhuyz/items/04a6871ae5f53ba5a97f)がとても分かりやすい

### AND
「must」「filter」どちらでも良い

「filter」の例はこんな感じ

```
SearchSourceBuilder searchBuilder = SearchSourceBuilder.searchSource();

BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
QueryBuilder query1 = QueryBuilders.matchQuery("message", "JAVA");
QueryBuilder query2 = QueryBuilders.matchQuery("message", "REST");
boolQuery.filter(query1);
boolQuery.filter(query2);

searchBuilder.query(boolQuery);
```

### OR

```
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
QueryBuilder query1 = QueryBuilders.matchQuery("message", "JAVA");
QueryBuilder query2 = QueryBuilders.matchQuery("message", "REST");
boolQuery.should(query1);
boolQuery.should(query2);
```

### NOT

```
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
QueryBuilder query = QueryBuilders.termQuery("user", "qiita");
boolQuery.mustNot(query);
```


# JSON形式で検索するには？

下記のようにJSON形式でも取得することができる
[Search Template API](https://www.elastic.co/guide/en/elasticsearch/client/java-rest/7.5/java-rest-high-search-template.html)

```
SearchTemplateRequest request = new SearchTemplateRequest();
request.setRequest(new SearchRequest("qiita"));

request.setScriptType(ScriptType.INLINE);
request.setScript(
                    "{" +
                            "  \"query\": { \"match\" : { \"{{field}}\" : \"{{value}}\" } }," +
                            "  \"size\" : \"{{size}}\"" +
                            "}");

Map<String, Object> scriptParams = new HashMap<>();
scriptParams.put("field", "message");
scriptParams.put("value", "REST");
scriptParams.put("size", 5);
request.setScriptParams(scriptParams);

SearchTemplateResponse response = client.searchTemplate(request, RequestOptions.DEFAULT);
SearchResponse searchResponse = response.getResponse();
SearchHit[] results = searchResponse.getHits().getHits();

```

# Frozen indices
6.8.1の「Java High Level REST Client」より使用することができる
Elasticsaerchに「Frozen indices」の実装された時には反映されていないため注意が必要

[How to Search Freeze Index using Java High Level REST Client](https://discuss.elastic.co/t/how-to-search-freeze-index-using-java-high-level-rest-client/189275/3)


```
SearchRequest request = new SearchRequest("qiita").source(searchBuilder);
request.indicesOptions(IndicesOptions.fromOptions(
                true,
                true,
                true,
                false,
                request.indicesOptions().allowAliasesToMultipleIndices(),
                request.indicesOptions().forbidClosedIndices(),
                request.indicesOptions().ignoreAliases(),
                true // ←ここでfrozen indexを検索できるか設定　true:検索可能
        ));

```





# 参考資料
* [ElasticsearchのBulk API効果を実測する](http://n-agetsuma.hatenablog.com/entry/2017/11/13/211310)
* [Elasticsearch 6.4基本操作 - Java版](https://www.cnblogs.com/swordfall/p/9981883.html)
* [今日のElasticsearch学び Vol.3 - クエリ編](https://blog.shibayu36.org/entry/2016/08/04/101500)

# 終わりに

「Java High Level REST Client」の日本語記事が少なく、英語・中国記事を読むことが多いです。
ドキュメントに大体書いてあるのですが、新機能追加後は「Java High Level REST Client」では対応していないみたいなことがありソースやリリースノートを読まないといけないこともあります。

明日は[@NAO_MK2](https://qiita.com/NAO_MK2)さんです。