MacOS el capitan に Elasticsearch を インストールして、java クライアントから検索してみる | Monotalkで、
作成したJavaクライアントをページング検索できるようにしてみました。
実装した結果を以下に記載します。
前提
以下、OS、Java、Elasticsearch の Version について記載します。
-
OS
sw_vers ---------------------------- ProductName: Mac OS X ProductVersion: 10.12.3 BuildVersion: 16D32 ----------------------------
-
Java
java -version ---------------------------- java version "1.8.0_45" Java(TM) SE Runtime Environment (build 1.8.0_45-b14) Java HotSpot(TM) 64-Bit Server VM (build 25.45-b02, mixed mode) ----------------------------
-
Elasticsearch
elasticsearch -version ---------------------------- Version: 5.2.2, Build: f9d9b74/2017-02-24T17:26:45.835Z, JVM: 1.8.0_45 ----------------------------
-
Elasticsearch Java Dependency
<!-- https://mvnrepository.com/artifact/org.elasticsearch/elasticsearch --> <dependency> <groupId>org.elasticsearch</groupId> <artifactId>elasticsearch</artifactId> <version>5.2.2</version> </dependency> <dependency> <groupId>org.elasticsearch.client</groupId> <artifactId>transport</artifactId> <version>5.2.2</version> </dependency>
ページングで必要な処理
基本的なページング処理として、以下の処理が必要になるかと思います。
-
Limit、Offset を指定して、ページング表示件数分データを取得する処理
-
Count クエリで、データ全件の件数のみ取得する処理
あとは、個人的な実装都合で、keyword があれば、キーワード指定で検索、
キーワードがなければ、データ全件を取得する処理が必要になります。
ElasticSearch のクエリの記述
1. ElasticSearch での Limit Offset 指定
第4回 Elasticsearch 入門 検索の基本中の基本 | Developers.IO
でページングについて記載がありますが、limit
として、size 、 offset
として、from が指定できます。
json形式で記載すると、以下のように記述できます。
curl -XPOST 'localhost:9200/festival/_search?pretty' -d '
{
"query": { "match": { "name" : "ROCK" } },
"from": 0,
"size": 20
}'
--------------------------
{
"took" : 215,
"timed_out" : false,
"_shards" : {
"total" : 5,
"successful" : 5,
"failed" : 0
},
"hits" : {
"total" : 34,
"max_score" : 3.1631145,
"hits" : [ ...... ]
}
}
2. ElasticSearch で件数を取得する
elasticsearchとSQL対比しながら理解 - Qiita で、
件数取得について記載がありますが、 "size" : 0
とすると、件数のみの取得ができます。1
[1] size 1以上を指定した場合も、件数は取得できます。0 だと、検索結果なしでとれます。
curl -XPOST 'localhost:9200/festival/_search?pretty' -d '
{
"query": { "match": { "name" : "ROCK" } },
"from": 0,
"size": 0
}'
-------------------------------------------------
{
"took" : 34,
"timed_out" : false,
"_shards" : {
"total" : 5,
"successful" : 5,
"failed" : 0
},
"hits" : {
"total" : 34,
"max_score" : 0.0,
"hits" : [ ]
}
}
3. ElasticSearch で条件指定なしでデータを取得する
elasticsearchとSQL対比しながら理解 - Qiita に記載がありますが、 match_all
で条件なしでデータが取得できます。
curl -XPOST 'localhost:9200/festival/_search?pretty' -d '
{
"query": { "match_all": {} },
"from": 0,
"size": 20
}'
--------------------------------------------------
{
"took" : 274,
"timed_out" : false,
"_shards" : {
"total" : 5,
"successful" : 5,
"failed" : 0
},
"hits" : {
"total" : 547,
"max_score" : 1.0,
"hits" : [ ...... ]
}
}
}
Java Client の実装
TransportClientを使った検索クラス
実装したクラスは以下になります。
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.io.Resources;
import org.apache.commons.lang3.StringUtils;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.transport.TransportClient;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.transport.InetSocketTransportAddress;
import org.elasticsearch.script.ScriptType;
import org.elasticsearch.script.mustache.SearchTemplateRequestBuilder;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.transport.client.PreBuiltTransportClient;
import xyz.monotalk.festivals4partypeople.models.elasticsearch.dto.Festival;
import java.io.IOException;
import java.net.InetAddress;
import java.net.URL;
import java.net.UnknownHostException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
/**
* FestivalIndex
*/
public class FestivalIndex {
/**
* findByKeywordWithPaging
*
* @param keyword
* @param from
* @param size
* @return
*/
public List<Festival> findByKeywordWithPaging(String keyword, int from, int size) {
// on startup
TransportClient client = getTransportClient();
String scripts = StringUtils.isEmpty(keyword)
? getResourceAsString("scripts/Festival_findAllWithPaging.json")
: getResourceAsString("scripts/Festival_findByKeywordWithPaging.json");
SearchRequest searchRequest = new SearchRequest();
searchRequest.indices("festival");
Map<String, Object> template_params = new HashMap<>();
if (!StringUtils.isEmpty(keyword)) {
template_params.put("keyword", keyword);
}
template_params.put("from", from);
template_params.put("size", size);
SearchResponse sr = new SearchTemplateRequestBuilder(client)
.setScript(scripts)
.setScriptType(ScriptType.INLINE)
.setScriptParams(template_params)
.setRequest(searchRequest)
.get()
.getResponse();
SearchHit[] results = sr.getHits().getHits();
List<Festival> festivals = new ArrayList<>();
for (SearchHit hit : results) {
String sourceAsString = hit.getSourceAsString();
if (sourceAsString != null) {
ObjectMapper mapper = new ObjectMapper();
Festival fes = null;
try {
fes = mapper.readValue(sourceAsString, Festival.class);
} catch (IOException e) {
e.printStackTrace();
}
festivals.add(fes);
}
}
client.close();
return festivals;
}
/**
* getRecordCountByKeyword
*
* @param keyword
* @return
*/
public long getRecordCountByKeyword(String keyword) {
TransportClient client = getTransportClient();
String scripts = StringUtils.isEmpty(keyword)
? getResourceAsString("scripts/Festival_getAllRecordCount.json")
: getResourceAsString("scripts/Festival_getRecordCountByKeyword.json");
SearchRequest searchRequest = new SearchRequest();
searchRequest.indices("festival");
Map<String, Object> template_params = new HashMap<>();
if (!StringUtils.isEmpty(keyword)) {
template_params.put("keyword", keyword);
}
SearchResponse sr = new SearchTemplateRequestBuilder(client)
.setScript(scripts)
.setScriptType(ScriptType.INLINE)
.setScriptParams(template_params)
.setRequest(searchRequest)
.get()
.getResponse();
long hits = sr.getHits().getTotalHits();
client.close();
return hits;
}
/**
* 2度呼び出されるため、メソッド化
* getTransportClient
*
* @return
*/
private TransportClient getTransportClient() {
TransportClient client = null;
try {
client = new PreBuiltTransportClient(Settings.builder()
.put("cluster.name", "elasticsearch_clustername").put("client.transport.sniff", false)
.put("node.name", "JlcSImk")
.put("client.transport.ping_timeout", 20, TimeUnit.SECONDS).build())
.addTransportAddress(new InetSocketTransportAddress(InetAddress.getByName("10.0.1.4"), 9300));
} catch (UnknownHostException e) {
e.printStackTrace();
}
return client;
}
/**
* guava の Resourcesクラスを使って、src/main/resources配下のファイルをStringで取得
* getResourceAsString
*
* @param resourceName
* @return
*/
private String getResourceAsString(String resourceName) {
String scripts = null;
URL url = Resources.getResource(resourceName);
try {
scripts = Resources.toString(url, Charset.forName("utf-8"));
} catch (IOException e) {
e.printStackTrace();
}
return scripts;
}
}
- findByKeywordWithPagingの説明
キーワードを条件に、ページング指定検索できるメソッドです。
キーワードが空かどうかで、取得するスクリプトを切り替えるようにしました。2
[2] 何かもっと良い方法があるのかもしれません。
現状、私個人の環境だと、index が1つしかないため結果は変わりませんが、
SearchRequest searchRequest = new SearchRequest();
searchRequest.indices("festival");
指定しないと全 index を対象に検索する動作になるのかと思います。
- getRecordCountByKeywordの説明
キーワードを条件に、検索結果件数のみを返すメソッドです。
キーワードが空かどうかで、取得するスクリプトを切り替えます。
トータル件数は、SearchResponse から、getHits().getTotalHits();
で取得できました。
作成したスクリプトファイル
-
src/main/resources/scripts/Festival_findAllWithPaging.json
{ "query": { "match_all": {} }, "from": "{{from}}", "size": "{{size}}" }
-
src/main/resources/scripts/Festival_findByKeywordWithPaging.json
{ "query": { "match": { "name": "{{keyword}}" } }, "from": "{{from}}", "size": "{{size}}" }
-
src/main/resources/scripts/Festival_getAllRecordCount.json
{ "query": { "match_all": {} }, "size": 0 }
-
src/main/resources/scripts/Festival_getRecordCountByKeyword.json
{ "query": { "match": { "name": "{{keyword}}" } }, "size": 0 }
まとめ
SearchTemplateRequestBuilder
に拘ってページングクエリを作成しました。
結構冗長になってしまいましたが、今のところ、SearchTemplateRequestBuilder
の使用方法しかわからないので、
一旦これでいいかと思っています。
QueryBuilders
を使う形だと実はもっとすっきりかけるのかもしれません。
Count API は 以前は、CountResponse
を返すAPI があったようですが、
Java API changes | Elasticsearch Reference [5.2] | Elastic
に削除され、Search API に size 0 を指定して、件数取得してね。という記載がありました。
client.prepareSearch(indices).setSource(new SearchSourceBuilder().size(0).query(query)).get();
一旦、SearchTemplateRequestBuilder
ゴリ押しで実装は進めていこうかと思います。
以上です。
コメント