きっかけ
Elasticsearch APIをJavaで使って実装するにあたって、
基本的な使い方はQiitaにもあったり公式リファレンスにもありますが、
ちょっと複雑化したり、より実践的な記述やコードが記載されている記事が少ないので、本記事を執筆することにしました。
この記事の対象となる方
- Elasticsearch APIをJavaで使って開発をこれから行いたい・行う予定の方
- Elasticsearchビギナーの方
環境(開発当時)
- Elasticsearch APIバージョン:7.3.2
- Javaバージョン:8
- Spring Bootバージョン:1.5.15
- mavenバージョン:2.17
準備
<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に対してリクエストを送るための大本となるものみたいな感じだと思います。
SearchRequest searchRequest = new SearchRequest();
参考:https://www.elastic.co/guide/en/elasticsearch/client/java-rest/master/java-rest-high-search.html
SearchSourceBuilder
SearchSourceBuilder
は検索パラメータを追加するためのものです。
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へアクセスできます。
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
で指定できます。
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
で指定できます。
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の場合では指定なしで大丈夫です。
searchSourceBuilder.fetchSource(new String[]{"FashionItemId", "ItemPrice", "FashionItemSize",
"FashionItemLargeCategory", "FashionItemSmallCategory"},
"ExclusionFashionItemId");
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));
from & size
SQLでいうoffset & limitに該当するものです。
前述したSearchSourceBuilderに対して .from
.to
で指定できます。
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchSourceBuilder.from(0);
searchSourceBuilder.size(100);
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)
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)
)
)
"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
]
}
}
]
}
}
]
}
}
]
}
}
]
}
(すごい複雑で見にくい・・・・。)
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で叩いてみるなどしてクエリが正しいものか確認しましょう。
System.out.println(qb4.toString());
最後に
この記事はあくまでも初めて自分がさわった当時の情報などになります。
当時は記事も少なかったものの現在では調べれば良質な記事が多くなっているかもしれません。
少しでも初めてElasticsearchAPIを触る方の参考になれば幸いです。