Elasticsearch 是一个分布式、RESTful 风格的搜索和数据分析引擎,本教程从Java语言角度讲解如何操作Elasticsearch,如果不熟悉Elasticsearch,可以先学习 Elasticsearch教程,掌握基本概念和查询语法。
教程基于ES官方的Java REST Client 进行讲解,老的Java API, ES 7.0.0以后将会废弃,不推荐继续使用。
支持Elasticsearch 5.6.x 以上。
Java版本,最低要求是1.8
org.elasticsearch.client
elasticsearch-rest-high-level-client
7.8.1
你可以根据自己的ES版本选择对应的Java REST Client版本。
在操作ES之前需要创建一个client, ES请求都是通过client发送,通过client可以配置ES的服务地址、安全验证相关参数。
RestHighLevelClient client = new RestHighLevelClient(
RestClient.builder(
new HttpHost("localhost", 9200, "http"),
new HttpHost("localhost", 9201, "http")));
通常全局创建一个client即可,client内部维护了连接池,因此在不使用client的时候需要通过下面方式释放资源。
client.close();
// 创建Request对象, 准备创建的索引名为twitter
CreateIndexRequest request = new CreateIndexRequest("twitter");
// 设置Request参数
request.settings(Settings.builder()
.put("index.number_of_shards", 3) // 设置分区数
.put("index.number_of_replicas", 2) // 设置副本数
);
// 通过JSON字符串的方式,设置ES索引结构的mapping
// ps: 通常都是通过json配置文件加载索引mapping配置,不需要拼接字符串。
request.mapping(
"{\n" +
" \"properties\": {\n" +
" \"message\": {\n" +
" \"type\": \"text\"\n" +
" }\n" +
" }\n" +
"}",
XContentType.JSON);
// 执行请求,创建索引
CreateIndexResponse createIndexResponse = client.indices().create(request, RequestOptions.DEFAULT);
if (createIndexResponse.isAcknowledged()) {
// 创建成功
}
// 创建对应的Request请求,设置索引名为posts
IndexRequest request = new IndexRequest("posts");
// 设置文档id=1
request.id("1");
// 以json字符串的形式设置文档内容,也就是准备插入到ES中的数据
String jsonString = "{" +
"\"user\":\"kimchy\"," +
"\"postDate\":\"2013-01-30\"," +
"\"message\":\"trying out Elasticsearch\"" +
"}";
request.source(jsonString, XContentType.JSON);
// 执行请求
IndexResponse indexResponse = client.index(request, RequestOptions.DEFAULT);
根据id查询一条数据
// 创建对应的Request对象,设置索引名为posts, 文档id=1
GetRequest getRequest = new GetRequest(
"posts",
"1");
// 执行ES请求
GetResponse getResponse = client.get(getRequest, RequestOptions.DEFAULT);
// 处理查询结果
String index = getResponse.getIndex();
String id = getResponse.getId();
// 检查文档是否存在
if (getResponse.isExists()) {
long version = getResponse.getVersion();
// 获取文档数据的json字符串形式,可以使用json库转换成Java对象
String sourceAsString = getResponse.getSourceAsString();
// 获取文档数据的Map形式
Map sourceAsMap = getResponse.getSourceAsMap();
// 获取文档数据的字节数组形式
byte[] sourceAsBytes = getResponse.getSourceAsBytes();
} else {
// 文档不存在
}
// 创建对应的Request对象,设置索引名为posts, 文档id=1
UpdateRequest request = new UpdateRequest("posts", "1");
// 以map形式,设置需要更新的文档字段
Map jsonMap = new HashMap<>();
jsonMap.put("updated", new Date());
jsonMap.put("reason", "daily update");
request.doc(jsonMap);
// 执行请求
UpdateResponse updateResponse = client.update(
request, RequestOptions.DEFAULT);
// 创建对应的Request对象,设置索引名为posts, 文档id=1
DeleteRequest request = new DeleteRequest(
"posts",
"1");
// 执行请求
DeleteResponse deleteResponse = client.delete(
request, RequestOptions.DEFAULT);
提示:详情,请参考后续章节。
本节主要讲解Java Elasticsearch RestHighLevelClient的配置详解。
RestHighLevelClient常用配置如下:
elasticsearch连接地址
elasticsearch账号/密码
Http请求头
连接超时
设置线程池大小
RestHighLevelClient 依赖 REST low-level client builder 进行配置,即依赖底层的RestClientBuilder对象进行参数设置
// 首先创建RestClientBuilder,后续章节通过RestClientBuilder对象进行参数配置。
RestClientBuilder restClientBuilder = RestClient.builder(
new HttpHost("localhost", 9200, "http"), // 设置ES服务地址,支持多个
new HttpHost("localhost", 9201, "http"));
// 创建RestHighLevelClient,•请求都是通过RestHighLevelClient实例发出去的。
RestHighLevelClient client = new RestHighLevelClient(restClientBuilder);
restClientBuilder.setHttpClientConfigCallback(new RestClientBuilder.HttpClientConfigCallback() {
@Override
public HttpAsyncClientBuilder customizeHttpClient(
HttpAsyncClientBuilder httpClientBuilder) {
// 通过CredentialsProvider实例,配置账号和密码
CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
credentialsProvider.setCredentials(AuthScope.ANY,
new UsernamePasswordCredentials("user", "password"));
// 设置安全验证凭证
return httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider);
}
});
// 创建header数组,可以设置多个header
Header[] defaultHeaders = new Header[]{new BasicHeader("header", "value")};
// 设置http header
restClientBuilder.setDefaultHeaders(defaultHeaders);
配置Elasticsearch连接超时时间
restClientBuilder.setRequestConfigCallback(
new RestClientBuilder.RequestConfigCallback() {
@Override
public RequestConfig.Builder customizeRequestConfig(
RequestConfig.Builder requestConfigBuilder) {
return requestConfigBuilder
.setConnectTimeout(5000) // 设置连接超时时间,5秒
.setSocketTimeout(60000); // 设置请求超时时间,1分种
}
});
设置线程池大小
restClientBuilder.setHttpClientConfigCallback(new RestClientBuilder.HttpClientConfigCallback() {
@Override
public HttpAsyncClientBuilder customizeHttpClient(
HttpAsyncClientBuilder httpClientBuilder) {
return httpClientBuilder.setDefaultIOReactorConfig(
IOReactorConfig.custom()
.setIoThreadCount(10) // 设置线程数
.build());
}
});
Java Elasticsearch Index API 主要用于插入或者更新文档数据。
// 创建Index Request,设置索引名为: posts
IndexRequest request = new IndexRequest("posts");
// 设置文档ID
request.id("1");
支持以JSON字符串形式或者map形式设置文档内容。
Json字符串形式:
String jsonString = "{" +
"\"user\":\"kimchy\"," +
"\"postDate\":\"2013-01-30\"," +
"\"message\":\"trying out Elasticsearch\"" +
"}";
request.source(jsonString, XContentType.JSON);
Map形式:
// 创建map
Map jsonMap = new HashMap<>();
jsonMap.put("user", "kimchy");
jsonMap.put("postDate", new Date());
jsonMap.put("message", "trying out Elasticsearch");
// 设置文档内容
request.source(jsonMap);
设置路由字段
request.routing("routing");
设置单个请求超时参数
request.timeout(TimeValue.timeValueSeconds(1));
request.timeout("1s");
设置文档版本
request.version(2);
Index api支持两类操作:create 或者 index (默认)
request.opType("create");
以同步的方式执行ES请求
IndexResponse indexResponse = client.index(request, RequestOptions.DEFAULT);
client.indexAsync(request, RequestOptions.DEFAULT, new ActionListener() {
@Override
public void onResponse(IndexResponse indexResponse) {
// 请求成功回调函数
}
@Override
public void onFailure(Exception e) {
// 请求失败回调函数
}
});
// 获取索引名
String index = indexResponse.getIndex();
// 获取文档ID
String id = indexResponse.getId();
if (indexResponse.getResult() == DocWriteResponse.Result.CREATED) {
// 成功创建文档
} else if (indexResponse.getResult() == DocWriteResponse.Result.UPDATED) {
// 成功更新文档
}
Get Api 主要用于根据文档ID查询索引数据。
// 创建GetRequest,索引名=posts, 文档ID=1
GetRequest getRequest = new GetRequest(
"posts",
"1");
默认返回文档内容
// 不返回文档内容
request.fetchSourceContext(FetchSourceContext.DO_NOT_FETCH_SOURCE);
// 设置返回指定字段
String[] includes = new String[]{"message", "*Date"};
String[] excludes = Strings.EMPTY_ARRAY;
FetchSourceContext fetchSourceContext =
new FetchSourceContext(true, includes, excludes);
request.fetchSourceContext(fetchSourceContext);
String[] includes = Strings.EMPTY_ARRAY;
// 过滤指定字段
String[] excludes = new String[]{"message"};
FetchSourceContext fetchSourceContext =
new FetchSourceContext(true, includes, excludes);
request.fetchSourceContext(fetchSourceContext);
request.routing("routing");
设置文档版本
request.version(2);
以同步的方式执行请求
GetResponse getResponse = client.get(getRequest, RequestOptions.DEFAULT);
client.getAsync(request, RequestOptions.DEFAULT, new ActionListener< GetResponse>() {
@Override
public void onResponse(GetResponse getResponse) {
// 请求成功回调函数
}
@Override
public void onFailure(Exception e) {
// 请求失败回调函数
}
});
String index = getResponse.getIndex();
String id = getResponse.getId();
// 检测索引是否存在
if (getResponse.isExists()) {
// 获取版本号
long version = getResponse.getVersion();
// 获取文档内容,json字符串形式
String sourceAsString = getResponse.getSourceAsString();
// 获取文档内容,map形式
Map sourceAsMap = getResponse.getSourceAsMap();
// 获取文档内容,字节数组形式
byte[] sourceAsBytes = getResponse.getSourceAsBytes();
} else {
}
Delete API 主要用于根据文档ID删除索引文档。
// 设置索引名=posts, 文档id=1
DeleteRequest request = new DeleteRequest(
"posts",
"1");
request.routing("routing");
设置单个请求超时参数
request.timeout(TimeValue.timeValueMinutes(2)); //格式1: 2分钟
request.timeout("2m"); ///格式2:2分钟
设置文档版本号
request.version(2);
DeleteResponse deleteResponse = client.delete(
request, RequestOptions.DEFAULT);
client.deleteAsync(request, RequestOptions.DEFAULT, new ActionListener() {
@Override
public void onResponse(DeleteResponse deleteResponse) {
// 请求成功回调函数
}
@Override
public void onFailure(Exception e) {
// 请求失败回调函数
}
});
Elasticsearch Update API 根据文档ID更新文档内容,主要支持两种方式更新文档内容:通过脚本的方式更新和更新文档部分字段。
提示:如果被更新的文档不存在,也支持插入操作,通过upsert api实现。
// 创建UpdateRequest请求,索引名=posts,文档ID=1
UpdateRequest request = new UpdateRequest(
"posts",
"1");
UpdateRequest对象支持下面几种更新文档内容的方式,根据需要选择一种方式即可
通过ES 内置script脚本更新文档内容。
// 定义脚本参数
Map parameters = singletonMap("count", 4);
// 创建inline脚本,使用painless语言,实现field字段 + count参数值
Script inline = new Script(ScriptType.INLINE, "painless",
"ctx._source.field += params.count", parameters);
// 设置脚本
request.script(inline);
通过map对象更新文档部分内容
// 通过map对象设置需要更新的字段内容
Map jsonMap = new HashMap<>();
jsonMap.put("updated", new Date());
jsonMap.put("reason", "daily update");
// 设置更新内容
request.doc(jsonMap);
通过json字符串方式更新文档部分内容
String jsonString = "{" +
"\"updated\":\"2017-01-01\"," +
"\"reason\":\"daily update\"" +
"}";
request.doc(jsonString, XContentType.JSON);
通过Upsert方式更新文档内容,跟前面三种类似,支持json字符串、map、脚本方式,但是有一点区别,如果被更新的文档不存在,则会执行插入操作。
String jsonString = "{\"created\":\"2017-01-01\"}";
request.upsert(jsonString, XContentType.JSON);
request.routing("routing");
设置请求超时时间
request.timeout(TimeValue.timeValueSeconds(1)); // 方式1:1秒
request.timeout("1s"); // 方式2:1秒
如果更新文档的时候出现版本冲突,重试几次。
request.retryOnConflict(3);
设置并发控制参数,新版的ES已经废弃老的version字段,详情请参考:ES基于乐观锁的并发控制
// 设置版本号
request.setIfSeqNo(2L);
// 设置文档所在的主分区
request.setIfPrimaryTerm(1L);
UpdateResponse updateResponse = client.update(
request, RequestOptions.DEFAULT);
client.updateAsync(request, RequestOptions.DEFAULT, new ActionListener() {
@Override
public void onResponse(UpdateResponse updateResponse) {
// 执行成功
}
@Override
public void onFailure(Exception e) {
// 执行失败
}
});
// 索引名
String index = updateResponse.getIndex();
// 文档id
String id = updateResponse.getId();
// 版本号
long version = updateResponse.getVersion();
if (updateResponse.getResult() == DocWriteResponse.Result.CREATED) {
// 成功创建文档
} else if (updateResponse.getResult() == DocWriteResponse.Result.UPDATED) {
// 成功更新文档
}
ES update by query api主要用于批量更新文档内容,支持设置查询条件限制更新文档的范围。
// 创建UpdateByQueryRequest对象,设置索引名,支持一次更新多个索引
// 同时更新source1和source2索引内容
UpdateByQueryRequest request =
new UpdateByQueryRequest("source1", "source2");
批量更新内容的时候,可能会遇到文档版本冲突的情况,需要设置版本冲突的时候如何处理。
版本冲突解决方案如下:
proceed - 忽略版本冲突,继续执行
abort - 遇到版本冲突,中断执行
request.setConflicts("proceed");
// 设置term查询条件,查询user字段=kimchy的文档内容
request.setQuery(new TermQueryBuilder("user", "kimchy"));
ES的查询语法是非常丰富的,这里仅给出一种写法,JAVA ES查询用法请参考后续的章节。
可以限制批量更新文档的数量
request.setMaxDocs(10);
Update by query api更新文档内容,仅支持通过脚本的方式修改文档内容。
request.setScript(
new Script( // 创建inline脚本,使用painless语言。
ScriptType.INLINE, "painless",
"if (ctx._source.user == 'kimchy') {ctx._source.likes++;}",
Collections.emptyMap()));
BulkByScrollResponse bulkResponse =
client.updateByQuery(request, RequestOptions.DEFAULT);
TimeValue timeTaken = bulkResponse.getTook(); // 更新花费时间
boolean timedOut = bulkResponse.isTimedOut(); // 是否超时
long totalDocs = bulkResponse.getTotal(); // 更新文档总数
long updatedDocs = bulkResponse.getUpdated(); // 成功更新了多少文档
long deletedDocs = bulkResponse.getDeleted(); // 删除了多少文档
long versionConflicts = bulkResponse.getVersionConflicts(); // 版本冲突次数
Java ES Delete By Query API 主要用于批量删除操作,支持设置ES查询条件。
// 创建 DeleteByQueryRequest 对象,设置批量删除的索引名为:source1和source2
// ps: 支持同时操作多个索引
DeleteByQueryRequest request =
new DeleteByQueryRequest("source1", "source2");
批量更新内容的时候,可能会遇到文档版本冲突的情况,需要设置版本冲突的时候如何处理。
版本冲突解决方案如下:
proceed - 忽略版本冲突,继续执行
abort - 遇到版本冲突,中断执行
request.setConflicts("proceed");
// 设置term查询条件,查询user字段=kimchy的文档内容
request.setQuery(new TermQueryBuilder("user", "kimchy"));
ES的查询语法是非常丰富的,这里仅给出一种写法,JAVA ES查询用法请参考后续的章节。
可以限制批量删除文档的数量
request.setMaxDocs(10);
BulkByScrollResponse bulkResponse =
client.deleteByQuery(request, RequestOptions.DEFAULT);
TimeValue timeTaken = bulkResponse.getTook(); // 批量操作消耗时间
boolean timedOut = bulkResponse.isTimedOut(); // 是否超时
long totalDocs = bulkResponse.getTotal(); // 涉及文档总数
long deletedDocs = bulkResponse.getDeleted(); // 成功删除文档数量
long versionConflicts = bulkResponse.getVersionConflicts(); // 版本冲突次数
Multi-Get API 主要用于根于id集合,批量查询文档内容,支持查询多个索引内容。
MultiGetRequest request = new MultiGetRequest();
// 通过MultiGetRequest.Item对象设置查询参数
request.add(new MultiGetRequest.Item(
"index", // 索引名
"12345")); // 文档id
// 添加另外一组查询参数,索引名=index, 索引Id=another_id
request.add(new MultiGetRequest.Item("index", "another_id"));
MultiGetResponse response = client.mget(request, RequestOptions.DEFAULT);
// response.getResponses返回多个MultiGetItemResponse对象,每个MultiGetItemResponse对象代表一个查询结果,这里以其中一个结果为例。
// ps: 通常是需要循环遍历response.getResponses返回的结果
MultiGetItemResponse firstItem = response.getResponses()[0];
assertNull(firstItem.getFailure());
GetResponse firstGet = firstItem.getResponse();
String index = firstItem.getIndex(); // 获取索引名
String id = firstItem.getId();// 获取文档Id
if (firstGet.isExists()) { // 检测文档是否存在
long version = firstGet.getVersion(); // 获取版本号
// 查询文档内容,json字符串格式
String sourceAsString = firstGet.getSourceAsString();
// 查询文档内容,Map对象格式
Map sourceAsMap = firstGet.getSourceAsMap();
} else {
}
ES的Bulk API主要用于在单个请求中,批量执行创建、更新、删除文档操作,避免循环发送大量的ES请求。
BulkRequest request = new BulkRequest();
支持index/update/delete操作。
// 通过add方法,添加IndexRequest对象,创建文档,下面插入3个文档
// ps: IndexRequest对象,以键值对的方式设置文档内容
request.add(new IndexRequest("posts").id("1")
.source(XContentType.JSON,"field", "foo"));
request.add(new IndexRequest("posts").id("2")
.source(XContentType.JSON,"field", "bar"));
request.add(new IndexRequest("posts").id("3")
.source(XContentType.JSON,"field", "baz"));
批量执行文档的删除、更新、创建操作
request.add(new DeleteRequest("posts", "3"));
request.add(new UpdateRequest("posts", "2")
.doc(XContentType.JSON,"other", "test"));
request.add(new IndexRequest("posts").id("4")
.source(XContentType.JSON,"field", "baz"));
设置请求超时时间
request.timeout(TimeValue.timeValueMinutes(2)); // 形式1:2分钟
request.timeout("2m"); // 形式2:2分钟
BulkResponse bulkResponse = client.bulk(request, RequestOptions.DEFAULT);
if (bulkResponse.hasFailures()) {
// 至少存在一个错误处理
}
// 循环检测批量操作结果
for (BulkItemResponse bulkItemResponse : bulkResponse) {
DocWriteResponse itemResponse = bulkItemResponse.getResponse();
// 根据操作类型检测执行结果
switch (bulkItemResponse.getOpType()) {
case INDEX:
case CREATE:
// 处理创建请求
IndexResponse indexResponse = (IndexResponse) itemResponse;
break;
case UPDATE:
// 处理更新请求
UpdateResponse updateResponse = (UpdateResponse) itemResponse;
break;
case DELETE:
// 处理删除请求
DeleteResponse deleteResponse = (DeleteResponse) itemResponse;
}
}
Elasticsearch的查询功能非常灵活,不了解Elasticsearch查询语法和概念,可以先阅读:ES查询语法
下面介绍Java Elasticsearch查询的写法。
实现ES查询的关键步骤:
创建RestHighLevelClient,Java ES Client对象 - (这个就不再重复,参考Java Elasticsearch Client配置)
创建SearchRequest对象 - 负责设置搜索参数
通过Client对象发送请求
处理搜索结果
创建SearchRequest对象步骤如下:
创建SearchRequest对象
通过SearchSourceBuilder设置搜索参数
将SearchSourceBuilder绑定到SearchRequest对象
创建SearchRequest对象,索引名=posts
SearchRequest searchRequest = new SearchRequest("posts");
SearchRequest负责设置搜索参数,包括:ES Query、分页参数等等常用设置。
实际上大部分SearchRequest对象的搜索参数都是通过SearchSourceBuilder对象间接设置。
通过SearchSourceBuilder间接完成搜索参数的设置
// 创建SearchSourceBuilder,构建ES搜索
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
// 设置ES查询条件
sourceBuilder.query(QueryBuilders.termQuery("user", "kimchy"));
// 设置分页参数,偏移从0开始
sourceBuilder.from(0);
// 设置分页参数,分页大小=5
sourceBuilder.size(5);
// 设置请求超时时间,60秒
sourceBuilder.timeout(new TimeValue(60, TimeUnit.SECONDS));
// 最后完成SearchRequest请求的参数设置
searchRequest.source(sourceBuilder);
通过上面例子可以知道,Java ES查询条件是通过QueryBuilders工具类构建的,QueryBuilders工具类,支持丰富ES查询条件,详情后面的章节会单独介绍,这里大家只要记得它的作用即可。
// 例子-构建一个Match模糊查询
QueryBuilder matchQueryBuilder = QueryBuilders.matchQuery("user", "kimchy")
.fuzziness(Fuzziness.AUTO)
.prefixLength(3)
.maxExpansions(10);
// 将QueryBuilders构建的查询,绑定到SearchSourceBuilder对象
sourceBuilder.query(matchQueryBuilder);
提示: QueryBuilder是所有Java ES查询的基础接口
通过SearchSourceBuilder设置搜索结果分页参数
// 设置分页参数,偏移从0开始
sourceBuilder.from(0);
// 设置分页参数,分页大小=5
sourceBuilder.size(5);
// 根据id字段,升序
builder.sort("id", SortOrder.ASC);
SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
RestStatus status = searchResponse.status(); // ES请求状态
TimeValue took = searchResponse.getTook(); // 执行时间
Boolean terminatedEarly = searchResponse.isTerminatedEarly();
boolean timedOut = searchResponse.isTimedOut(); // 是否超时
// 获取hits,SearchHits对象包含搜索结果
SearchHits hits = searchResponse.getHits();
TotalHits totalHits = hits.getTotalHits();
// 搜索结果总数
long numHits = totalHits.value;
// 遍历搜索结果
SearchHit[] searchHits = hits.getHits();
for (SearchHit hit : searchHits) {
// 获取文档内容,json字符串格式
String sourceAsString = hit.getSourceAsString();
// 获取文档内容,Map对象格式
Map sourceAsMap = hit.getSourceAsMap();
}
我们先通过一个简单的ES查询例子,了解Java ES查询的基本写法。
// 首先创建RestClient,后续章节通过RestClient对象进行参数配置。
RestClientBuilder restClientBuilder = RestClient.builder(
new HttpHost("localhost", 9200, "http"), // 设置ES服务地址,支持多个
new HttpHost("localhost", 9201, "http"));
// 创建RestHighLevelClient,•请求都是通过RestHighLevelClient实例发出去的。
RestHighLevelClient client = new RestHighLevelClient(restClientBuilder);
// 创建SearchRequest对象
SearchRequest searchRequest = new SearchRequest();
// 通过SearchSourceBuilder构建搜索参数
SearchSourceBuilder builder = new SearchSourceBuilder();
// 通过QueryBuilders构建ES查询条件
builder.query(QueryBuilders.termsQuery("order_id", 1,2,3,4,5));
// 设置搜索参数
searchRequest.source(builder);
// 执行ES请求
SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
// 获取搜索到的文档
SearchHits hits = searchResponse.getHits();
// 遍历搜索结果
SearchHit[] searchHits = hits.getHits();
for (SearchHit hit : searchHits) {
// 获取文档内容,json字符串格式
String sourceAsString = hit.getSourceAsString();
// 获取文档内容,Map对象格式
Map sourceAsMap = hit.getSourceAsMap();
}
全文搜索是Elasticsearch的核心特性之一,Java Elasticsearch全文搜索查询主要由MatchQueryBuilder这个构造器配置。
有两种方式创建MatchQueryBuilder
直接实例化MatchQueryBuilder
通过QueryBuilders工具创建
直接实例化MatchQueryBuilder对象。 构造方法参数说明:
参数1 - 字段名
参数2 - 匹配关键词
MatchQueryBuilder matchQueryBuilder = new MatchQueryBuilder("title", "梯子教程");
通过QueryBuilders工具创建
// 可以直接赋值给QueryBuilder接口定义的对象
QueryBuilder matchQueryBuilder = QueryBuilders.matchQuery("title", "梯子教程");
Java 所有的ES查询请求都是通过SearchRequest对象进行设置,因此需要实例化SearchRequest对象,设置query参数。
SearchRequest searchRequest = new SearchRequest();
// 通过SearchSourceBuilder构建搜索参数
SearchSourceBuilder builder = new SearchSourceBuilder();
// 设置query参数,绑定前面创建的Query对象
builder.query(matchQueryBuilder);
// 设置SearchRequest搜索参数
searchRequest.source(builder);
// 执行ES请求
SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
提示: 如何处理查询结果,请参考 Java ES查询基本写法
Elasticsearch的match_phrase短语查询跟match的区别就是,关键词作为一个整体进行搜索,而不是拆分成一个个关键词。
有两种方式创建MatchPhraseQueryBuilder
直接实例化MatchPhraseQueryBuilder
通过QueryBuilders工具创建
直接实例化MatchPhraseQueryBuilder对象。 构造方法参数说明:
参数1 - 字段名
参数2 - 匹配短语
MatchPhraseQueryBuilder matchPhraseQueryBuilder = new MatchPhraseQueryBuilder("title", "Elasticsearch 查询语法");
通过QueryBuilders工具创建
// 可以直接赋值给QueryBuilder接口定义的对象
QueryBuilder matchPhraseQueryBuilder = QueryBuilders.matchPhraseQuery("title", "Elasticsearch 查询语法");
Java 所有的ES查询请求都是通过SearchRequest对象进行设置,因此需要实例化SearchRequest对象,设置query参数。
SearchRequest searchRequest = new SearchRequest();
// 通过SearchSourceBuilder构建搜索参数
SearchSourceBuilder builder = new SearchSourceBuilder();
// 设置query参数,绑定前面创建的Query对象
builder.query(matchPhraseQueryBuilder);
// 设置SearchRequest搜索参数
searchRequest.source(builder);
// 执行ES请求
SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
提示: 如何处理查询结果,请参考 Java ES查询基本写法
Elasticsearch的term查询,主要用于实现等值匹配,类似SQL的fieldname=value表达式。
等值匹配
// 方式1 TermQueryBuilder termQueryBuilder = new TermQueryBuilder("order_id", 100); // 方式2 TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("order_id", 100);
类似SQL:order_id=100 条件
Java 所有的ES查询请求都是通过SearchRequest对象进行设置,因此需要实例化SearchRequest对象,设置query参数。
SearchRequest searchRequest = new SearchRequest();
// 通过SearchSourceBuilder构建搜索参数
SearchSourceBuilder builder = new SearchSourceBuilder();
// 设置query参数,绑定前面创建的Query对象
builder.query(termQueryBuilder);
// 设置SearchRequest搜索参数
searchRequest.source(builder);
// 执行ES请求
SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
提示: 如何处理查询结果,请参考 Java ES查询基本写法
Elasticsearch terms查询,用于实现类似SQL的in语句,匹配其中一个值即可。
实现类似SQL的in语句
// 方式1
TermsQueryBuilder termsQueryBuilder = new TermsQueryBuilder("order_id", 1,2,3,4,5);
// 方式2
TermsQueryBuilder termsQueryBuilder = QueryBuilders.termsQuery("order_id", 1,2,3,4,5);
类似SQL:order_id in (1,2,3,4,5)
Java 所有的ES查询请求都是通过SearchRequest对象进行设置,因此需要实例化SearchRequest对象,设置query参数。
SearchRequest searchRequest = new SearchRequest();
// 通过SearchSourceBuilder构建搜索参数
SearchSourceBuilder builder = new SearchSourceBuilder();
// 设置query参数,绑定前面创建的Query对象
builder.query(termsQueryBuilder);
// 设置SearchRequest搜索参数
searchRequest.source(builder);
// 执行ES请求
SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
提示: 如何处理查询结果,请参考 Java ES查询基本写法
Elasticsearch的range范围匹配,可以实现类似SQL语句中的>, >=, <, <=关系表达式。
// 等价SQL: price > 100 and price < 200
RangeQueryBuilder rangeQueryBuilder = new RangeQueryBuilder("price");
rangeQueryBuilder.gt(100);
rangeQueryBuilder.lt(200);
// 等价SQL: price >= 100 and price <= 200
RangeQueryBuilder rangeQueryBuilder = new RangeQueryBuilder("price");
rangeQueryBuilder.gte(100);
rangeQueryBuilder.lte(200);
// 等价SQL: price >= 150 and price <= 300
RangeQueryBuilder rangeQueryBuilder = QueryBuilders.rangeQuery("price")
.gte(150)
.lte(300);
Java 所有的ES查询请求都是通过SearchRequest对象进行设置,因此需要实例化SearchRequest对象,设置query参数。
SearchRequest searchRequest = new SearchRequest();
// 通过SearchSourceBuilder构建搜索参数
SearchSourceBuilder builder = new SearchSourceBuilder();
// 设置query参数,绑定前面创建的Query对象
builder.query(rangeQueryBuilder);
// 设置SearchRequest搜索参数
searchRequest.source(builder);
执行请求
// 执行ES请求
SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
提示: 如何处理查询结果,请参考 Java ES查询基本写法
Elasticsearch bool组合查询,通过must(且)和should(或)逻辑运算组合term、terms、range等ES查询子句,实现复杂的查询需求,类似SQL的where子句。
不了解ES Bool查询,可以参考Elasticsearch Bool查询语法
// 等价SQL: shop_id=100 and status=3 or (price >= 100 and price <= 300)
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery()
.must(QueryBuilders.termQuery("shop_id", 100)) // 通过must设置term子查询
.must(QueryBuilders.termQuery("status", 3)) // 通过must设置term子查询
.should(QueryBuilders.rangeQuery("price").gte(100).lte(300));// 通过should设置range子查询
bool查询支持多层嵌套,最终组合出复杂的查询条件
Java 所有的ES查询请求都是通过SearchRequest对象进行设置,因此需要实例化SearchRequest对象,设置query参数。
SearchRequest searchRequest = new SearchRequest();
// 通过SearchSourceBuilder构建搜索参数
SearchSourceBuilder builder = new SearchSourceBuilder();
// 设置query参数,绑定前面创建的Query对象
builder.query(boolQueryBuilder);
// 设置SearchRequest搜索参数
searchRequest.source(builder);
// 执行ES请求
SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
提示:如何处理查询结果,请参考 Java ES查询基本写法
Java Elasticsearch geo查询主要包括:
按距离搜索
按距离排序
按矩形范围搜索
// 根据location坐标字段和当前位置,搜索1千米范围的数据
GeoDistanceQueryBuilder queryBuilder = QueryBuilders.geoDistanceQuery("location")
.distance("1km") //设置搜索距离为1千米
// 设置当前位置
.point(new GeoPoint(39.889916, 116.379547));
// 创建SearchRequest对象
SearchRequest searchRequest = new SearchRequest();
// 通过SearchSourceBuilder构建搜索参数
SearchSourceBuilder builder = new SearchSourceBuilder();
// 设置前面创建的ES查询条件
builder.query(queryBuilder);
// 构建GeoDistanceSortBuilder设置按距离排序参数
GeoDistanceSortBuilder geoDistanceSortBuilder = SortBuilders.geoDistanceSort("location", new GeoPoint(39.889916, 116.379547));
// 升序排序
geoDistanceSortBuilder.order(SortOrder.ASC);
// 设置排序参数
builder.sort(geoDistanceSortBuilder);
// 设置搜索请求参数
searchRequest.source(builder);
// 根据location坐标字段和一个矩形范围,匹配文档
GeoBoundingBoxQueryBuilder queryBuilder = QueryBuilders.geoBoundingBoxQuery("location")
.setCorners( // 设置矩形坐标
new GeoPoint(40.73, -74.1), // 设置矩形的左上角坐标
new GeoPoint(40.717, -73.99) // 设置矩形的右下角坐标
);
Java 所有的ES查询请求都是通过SearchRequest对象进行设置,因此需要实例化SearchRequest对象,设置query参数。
SearchRequest searchRequest = new SearchRequest();
// 通过SearchSourceBuilder构建搜索参数
SearchSourceBuilder builder = new SearchSourceBuilder();
// 设置query参数,绑定前面创建的Query对象
builder.query(queryBuilder);
// 设置SearchRequest搜索参数
searchRequest.source(builder);
// 执行ES请求
SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
提示:如何处理查询结果,请参考 Java ES查询基本写法
Elasticsearch中的聚合查询,类似SQL的SUM/AVG/COUNT/GROUP BY分组查询,主要用于统计分析场景。
这里主要介绍Java Elasticsearch 聚合查询的写法,如果不了解ES聚合查询,请参考 ES聚合查询基本概念和用法。
例子
import org.apache.http.HttpHost;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestClientBuilder;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.*;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
import org.elasticsearch.search.aggregations.metrics.Avg;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import java.io.IOException;
public class Main {
public static void main(String[] args) throws IOException {
// 首先创建RestClient,后续章节通过RestClient对象进行参数配置。
RestClientBuilder restClientBuilder = RestClient.builder(
new HttpHost("localhost", 9200, "http"), // 设置ES服务地址,支持多个
new HttpHost("localhost", 9201, "http"));
// 创建RestHighLevelClient,•请求都是通过RestHighLevelClient实例发出去的。
RestHighLevelClient client = new RestHighLevelClient(restClientBuilder);
// 创建SearchRequest对象, 设置查询索引名=order
SearchRequest searchRequest = new SearchRequest("order");
// 通过SearchSourceBuilder构建搜索参数
SearchSourceBuilder builder = new SearchSourceBuilder();
// 通过QueryBuilders构建ES查询条件,这里查询所有文档,复杂的查询语句设置请参考前面的章节。
builder.query(QueryBuilders.matchAllQuery());
// 创建terms桶聚合,聚合名字=by_shop, 字段=shop_id,根据shop_id分组
TermsAggregationBuilder aggregationBuilder = AggregationBuilders.terms("by_shop")
.field("shop_id");
// 嵌套聚合
// 设置Avg指标聚合,聚合名字=avg_price, 字段=price,计算平均价格
aggregationBuilder.subAggregation(AggregationBuilders.avg("avg_price").field("price"));
// 设置聚合查询
builder.aggregation(aggregationBuilder);
// 设置搜索条件
searchRequest.source(builder);
// 如果只想返回聚合统计结果,不想返回查询结果可以将分页大小设置为0
builder.size(0);
// 执行ES请求
SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
// 处理聚合查询结果
Aggregations aggregations = searchResponse.getAggregations();
// 根据by_shop名字查询terms聚合结果
Terms byShopAggregation = aggregations.get("by_shop");
// 遍历terms聚合结果
for (Terms.Bucket bucket : byShopAggregation.getBuckets()) {
// 因为是根据shop_id分组,因此可以直接将桶的key转换成int类型
int shopId = bucket.getKeyAsNumber().intValue();
// 根据avg_price聚合名字,获取嵌套聚合结果
Avg avg = bucket.getAggregations().get("avg_price");
// 获取平均价格
double avgPrice = avg.getValue();
}
// 关闭ES Client
client.close();
}
}
例子聚合统计的效果等价SQL:
select shop_id, avg(price) as avg_price from order group by shop_id
大家可以先通过例子和注释大致了解 Java ES 聚合查询的基本写法
Elasticsearch指标聚合,就是类似SQL的统计函数,指标聚合可以单独使用,也可以跟桶聚合一起使用,下面介绍Java Elasticsearch指标聚合的写法。
不了解ES指标聚合相关知识,先看一下Elasticsearch 指标聚合教程
/
/ 首先创建RestClient,后续章节通过RestClient对象进行参数配置。
RestClientBuilder restClientBuilder = RestClient.builder(
new HttpHost("localhost", 9200, "http"), // 设置ES服务地址,支持多个
new HttpHost("localhost", 9201, "http"));
// 创建RestHighLevelClient,•请求都是通过RestHighLevelClient实例发出去的。
RestHighLevelClient client = new RestHighLevelClient(restClientBuilder);
// 创建SearchRequest对象, 索引名=order
SearchRequest searchRequest = new SearchRequest("order");
// 通过SearchSourceBuilder构建搜索参数
SearchSourceBuilder builder = new SearchSourceBuilder();
// 通过QueryBuilders构建ES查询条件,这里查询所有文档,复杂的查询语句设置请参考前面的章节。
builder.query(QueryBuilders.matchAllQuery());
// 创建Value Count指标聚合
// 聚合统计命名为:orders, 统计order_id字段值的数量
ValueCountAggregationBuilder valueCountAggregationBuilder = AggregationBuilders.count("orders")
.field("order_id");
// 创建Sum指标聚合
// 聚合统计命名为:total_sale, 统计price字段值的总和
SumAggregationBuilder sumAggregationBuilder = AggregationBuilders.sum("total_sale")
.field("price");
// 设置聚合查询,可以设置多个聚合查询条件,只要聚合查询命名不同就行
builder.aggregation(valueCountAggregationBuilder);
builder.aggregation(sumAggregationBuilder);
// 设置搜索条件
searchRequest.source(builder);
// 执行ES请求
SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
// 处理聚合查询结果
Aggregations aggregations = searchResponse.getAggregations();
// 根据orders命名查询,ValueCount统计结果
ValueCount valueCount = aggregations.get("orders");
System.out.println(valueCount.getValue());
// 根据total_sale命名查询,Sum统计结果
Sum sum = aggregations.get("total_sale");
System.out.println(sum.getValue());
其他指标聚合的用法类似,后面分别介绍常用指标聚合。
值聚合,主要用于统计文档总数,类似SQL的count函数。
创建聚合条件
// 创建Value Count指标聚合
// 聚合统计命名为:orders, 统计order_id字段值的数量
ValueCountAggregationBuilder valueCountAggregationBuilder = AggregationBuilders.count("orders")
.field("order_id");
处理聚合结果
Aggregations aggregations = searchResponse.getAggregations();
// 根据orders命名查询,ValueCount统计结果
ValueCount valueCount = aggregations.get("orders");
// 打印结果
System.out.println(valueCount.getValue());
基数聚合,也是用于统计文档的总数,跟Value Count的区别是,基数聚合会去重,不会统计重复的值,类似SQL的count(DISTINCT 字段)用法。
基数聚合是一种近似算法,统计的结果会有一定误差,不过性能很好。
创建聚合条件
// 聚合统计命名为:total, 近似统计id字段值的数量
CardinalityAggregationBuilder cardinalityAggregationBuilder = AggregationBuilders.cardinality("total")
.field("id");
处理聚合结果
Aggregations aggregations = searchResponse.getAggregations();
// 根据total命名查询,Cardinality统计结果
Cardinality cardinality = aggregations.get("total");
// 打印结果
System.out.println(cardinality.getValue());
求平均值
创建聚合条件
AvgAggregationBuilder avgAggregationBuilder = AggregationBuilders.avg("avg_price")
.field("price");
处理聚合结果
Aggregations aggregations = searchResponse.getAggregations();
// 根据total命名查询,Avg统计结果
Avg avgPrice = aggregations.get("avg_price");
// 打印结果
System.out.println(avgPrice.getValue());
求和计算
创建聚合条件
SumAggregationBuilder sumAggregationBuilder = AggregationBuilders.sum("total_sale")
.field("price");
处理聚合结果
Aggregations aggregations = searchResponse.getAggregations();
// 根据total命名查询,Sum统计结果
Sum totalPrice = aggregations.get("total_sale");
// 打印结果
System.out.println(totalPrice.getValue());
求最大值
创建聚合条件
MaxAggregationBuilder maxAggregationBuilder = AggregationBuilders.max("max_price")
.field("price");
处理聚合结果
// 处理聚合查询结果
Aggregations aggregations = searchResponse.getAggregations();
// 根据max_price命名查询,Max统计结果
Max maxPrice = aggregations.get("max_price");
// 打印结果
System.out.println(maxPrice.getValue());
求最小值
创建聚合条件
MinAggregationBuilder minAggregationBuilder = AggregationBuilders.min("min_price")
.field("price");
处理聚合结果
Aggregations aggregations = searchResponse.getAggregations();
// 根据min_price命名查询,Min统计结果
Min minPrice = aggregations.get("min_price");
// 打印结果
System.out.println(minPrice.getValue());
Elasticsearch桶聚合,目的就是数据分组,先将数据按指定的条件分成多个组,然后对每一个组进行统计。
不了解Elasticsearch桶聚合概念,可以先学习下Elasticsearch桶聚合教程
本章介绍java elasticsearch桶聚合的用法
// 首先创建RestClient,后续章节通过RestClient对象进行参数配置。
RestClientBuilder restClientBuilder = RestClient.builder(
new HttpHost("localhost", 9200, "http"), // 设置ES服务地址,支持多个
new HttpHost("localhost", 9201, "http"));
// 创建RestHighLevelClient,•请求都是通过RestHighLevelClient实例发出去的。
RestHighLevelClient client = new RestHighLevelClient(restClientBuilder);
// 创建SearchRequest对象, 设置索引名=order
SearchRequest searchRequest = new SearchRequest("order");
// 通过SearchSourceBuilder构建搜索参数
SearchSourceBuilder builder = new SearchSourceBuilder();
// 通过QueryBuilders构建ES查询条件,这里查询所有文档,复杂的查询语句设置请参考前面的章节。
builder.query(QueryBuilders.matchAllQuery());
// 创建Terms桶聚合条件
// terms聚合命名为: by_shop, 分组字段为: shop_id
TermsAggregationBuilder byShop = AggregationBuilders.terms("by_shop")
.field("shop_id");
// 设置聚合条件
builder.aggregation(byShop);
// 设置搜索条件
searchRequest.source(builder);
// 执行ES请求
SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
// 处理聚合查询结果
Aggregations aggregations = searchResponse.getAggregations();
// 根据by_shop名字查询terms聚合结果
Terms byShopAggregation = aggregations.get("by_shop");
// 遍历terms聚合结果
for (Terms.Bucket bucket : byShopAggregation.getBuckets()) {
// 因为是根据shop_id分组,因此可以直接将桶的key转换成int类型
int shopId = bucket.getKeyAsNumber().intValue();
// 如果分组的字段是字符串类型,可以直接转成String类型
// String key = bucket.getKeyAsString();
// 获取文档总数
long count = bucket.getDocCount();
}
其他桶聚合条件的用法类型,下面分别介绍各类常用的桶聚合
创建聚合条件
// terms聚合命名为: by_shop, 分组字段为: shop_id
TermsAggregationBuilder byShop = AggregationBuilders.terms("by_shop")
.field("shop_id");
处理聚合结果
Aggregations aggregations = searchResponse.getAggregations();
// 根据by_shop命名查询terms聚合结果
Terms byShopAggregation = aggregations.get("by_shop");
// 遍历terms聚合结果
for (Terms.Bucket bucket : byShopAggregation.getBuckets()) {
// 因为是根据shop_id分组,因此可以直接将桶的key转换成int类型
int shopId = bucket.getKeyAsNumber().intValue();
// 如果分组的字段是字符串类型,可以直接转成String类型
// String key = bucket.getKeyAsString();
// 获取文档总数
long count = bucket.getDocCount();
}
创建聚合条件
// Histogram聚合命名为: prices
HistogramAggregationBuilder histogramAggregationBuilder = AggregationBuilders.histogram("prices")
.field("price") // 根据price字段值,对数据进行分组
.interval(100); // 分桶的间隔为100,意思就是price字段值按100间隔分组
处理聚合结果
// 处理聚合查询结果
Aggregations aggregations = searchResponse.getAggregations();
// 根据prices命名查询Histogram聚合结果
Histogram histogram = aggregations.get("prices");
// 遍历聚合结果
for (Histogram.Bucket bucket : histogram.getBuckets()) {
// 获取桶的Key值
String key = bucket.getKeyAsString();
// 获取文档总数
long count = bucket.getDocCount();
}
创建聚合条件
// DateHistogram聚合命名为: sales_over_time
DateHistogramAggregationBuilder dateHistogramAggregationBuilder = AggregationBuilders.dateHistogram("sales_over_time")
.field("date") // 根据date字段值,对数据进行分组
// 时间分组间隔:DateHistogramInterval.* 序列常量,支持每月,每年,每天等等时间间隔
.calendarInterval(DateHistogramInterval.MONTH)
// 设置返回结果中桶key的时间格式
.format("yyyy-MM-dd");
处理聚合结果
// 处理聚合查询结果
Aggregations aggregations = searchResponse.getAggregations();
// 根据sales_over_time命名查询Histogram聚合结果
Histogram histogram = aggregations.get("sales_over_time");
// 遍历聚合结果
for (Histogram.Bucket bucket : histogram.getBuckets()) {
// 获取桶的Key值
String key = bucket.getKeyAsString();
// 获取文档总数
long count = bucket.getDocCount();
}
创建聚合条件
//range聚合命名为: price_ranges
RangeAggregationBuilder rangeAggregationBuilder = AggregationBuilders.range("price_ranges")
.field("price") // 根据price字段分桶
.addUnboundedFrom(100) // 范围配置, 0 - 100
.addRange(100.0, 200.0) // 范围配置, 100 - 200
.addUnboundedTo(200.0); // 范围配置,> 200的值
处理聚合结果
// 处理聚合查询结果
Aggregations aggregations = searchResponse.getAggregations();
Range range = aggregations.get("price_ranges");
// 遍历聚合结果
for (Range.Bucket bucket : range.getBuckets()) {
// 获取桶的Key值
String key = bucket.getKeyAsString();
// 获取文档总数
long count = bucket.getDocCount();
}
桶聚合之间支持互相嵌套,同时桶聚合也可以嵌套多个指标聚合,可以参考下面例子组合聚合条件
创建嵌套聚合条件
// 创建Terms指标聚合
TermsAggregationBuilder byShop = AggregationBuilders.terms("by_shop")
.field("shop_id");
// 创建avg指标聚合
AvgAggregationBuilder avgPriceBuilder = AggregationBuilders.avg("avg_price")
.field("price");
// 设置嵌套聚合查询条件
byShop.subAggregation(avgPriceBuilder);
SumAggregationBuilder sumPriceBulder = AggregationBuilders.sum("sum_price")
.field("price");
// 设置嵌套聚合查询条件
byShop.subAggregation(sumPriceBulder);
处理结果
// 处理聚合查询结果
Aggregations aggregations = searchResponse.getAggregations();
Terms terms = aggregations.get("by_shop");
// 遍历聚合结果
for (Terms.Bucket bucket : terms.getBuckets()) {
// 获取桶的Key值
String key = bucket.getKeyAsString();
// 获取文档总数
long count = bucket.getDocCount();
// 处理嵌套聚合结果
Aggregations subAggregations = bucket.getAggregations();
// 根据avg_price命名,查询avg聚合结果
Avg avgPriceResult = subAggregations.get("avg_price");
// 获取平均价格
double avgPrice = avgPriceResult.getValue();
// 根据sum_price命名,查询sum聚合结果
Sum sumPriceResult = subAggregations.get("sum_price");
// 获取总价格
double sumPrice = sumPriceResult.getValue();
}