大家好我是迷途,一个在互联网行业,摸爬滚打的学子。热爱学习,热爱代码,热爱技术。热爱互联网的一切。再也不怕elasticsearch系列,帅途会慢慢由浅入深,为大家剖析一遍,各位大佬请放心,虽然这个系列帅途有时候更新的有点慢,但是绝对不会烂尾!如果你喜欢本系列的话,就快点赞关注收藏安排一波吧~
之前帅途用了大量的篇幅讲解了es中restapi的用法,所以本文不会用太多篇幅去讲解es的语法,如果不清楚的同学可以看看帅途之前的文章,es一些查询,进阶语法之类的都已经详细的描述过了。ES在7.X之后已经不在支持TransportClient进行连接了,本文主要讲解es新版本的连接方式,使用Java Rest Api连接es,
目前在官方文档中Es官方为我们提供了各种语言的集成方式。Java主要分为原生Api连接与Rest Api连接。目前官方推荐使用Rest Api进行集成。在Rest Api中又分为Low版本和High版本。High对Low版本进行了加强。做为帅途当然要使用High版本的啦~
High Rest Api 是基于Low Rest Api 封装的。他其实就是对我们使用Rest Api调用ES时在原本HTTP请求的基础之上,将我们的请求和响应封装成了对象。例如我们在HTTP请求中的聚合关键字aggs就被封装成了AggregationBuilders(聚合请求顶级父类对象,根据我们聚合请求的参数不同,有不同的子类)对象。所以大家在学习High语法的时候,看到大量的封装API,别怕,别头疼。帅途这里告诉大家一个小妙招:其实大部分Java代码中的语法跟咋门使用Rest Api访问es是一样的。我们根据我们Api中的关键字就可以找到大部分ES为我们封装的对象。另外,在我们每个Api的调用中,ES都为我们提供了两种调用方式,一种是同步,一种是异步,同步调用在请求之后会返回一个响应对象,也是由我们API封装接收,而异步调用则需要我们定义一个监听器,在收到响应或者错误是被通知。
1、引入Maven依赖
搭建spring boot环境帅途这里就不过多赘述了。不会的小伙伴可以自行查阅一下资料
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-elasticsearchartifactId>
<version>2.3.0.RELEASEversion>
dependency>
在我们引入es依赖的时候有一个小注意点。我们jar包中es的版本需要和我们安装的es版本对应,否则可能会出现Api无法调用等问题,在properties中控制es版本
<properties>
<java.version>1.8java.version>
<elasticsearch.version>7.3.2elasticsearch.version>
properties>
2、定义config配置对象,配置es连接信息,并加入spring 容器管理
import org.apache.http.HttpHost;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.impl.nio.client.HttpAsyncClientBuilder;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestClientBuilder;
import org.elasticsearch.client.RestHighLevelClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
// 配置注解
@Configuration
public class ElasticSearchClientConfig {
//注入es高级客户端
@Bean
public RestHighLevelClient restHighLevelClient(){
// 定义连接地址,如果是多个就配置多个HttpHost,由于我们使用的是RestApi进行访问所以使用的是Http方式9200(使用Java Api则是9300端口)
RestClientBuilder builder = RestClient.builder(
new HttpHost("112.74.48.31", 9200, "http"),
new HttpHost("112.74.48.31", 9200, "http"));
// 一些其他连接配置,例如超时时间等
builder.setRequestConfigCallback(
new RestClientBuilder.RequestConfigCallback() {
@Override
public RequestConfig.Builder customizeRequestConfig(
RequestConfig.Builder requestConfigBuilder) {
return requestConfigBuilder.setSocketTimeout(10000);
}
});
// 异步调用配置,配置监听器等
// 由于未设置监听器,所以不开放该配置
/*builder.setHttpClientConfigCallback(new RestClientBuilder.HttpClientConfigCallback() {
@Override
public HttpAsyncClientBuilder customizeHttpClient(
HttpAsyncClientBuilder httpClientBuilder) {
return httpClientBuilder.setProxy(
new HttpHost("112.74.48.31", 8080, "http"));
}
});*/
RestHighLevelClient client = new RestHighLevelClient(builder);
return client;
}
}
帅途这里列举了部分常用配置,需要定义一些其他配置的小伙伴可以参考一下官方文档API里面的配置,注:由于是High版本集成了Low版本,所以在High版本的文档里面是没有描述特定配置的,我们需要去Low版本文档中查阅,更多es拓展配置点这里。
由于我们之前将RestHighLevelClient配置到spring。让spring对象工厂为我们控制,所以这里直接注入即可
@Autowired
private RestHighLevelClient restHighLevelClient;
在我们使用High客户端操作索引的时候,只需要记住一个对象,然后根据Rest Api联想即可。IndexRequest对象(操作索引对象,我们操作索引的对象根据操作的不同有不同的对象,例如删除索引是DeleteIndexRequest,创建索引是CreateindexRequest),我们在调用时则使用restHighLevelClient.indices()方法获取index执行器,然后跟我我们的操作执行,例:restHighLevelClient.indices().create()创建索引,restHighLevelClient.indices().get()获取索引。
1、创建索引
/**
*
* 参数解析:
* new new CreateIndexRequest("metoo"); // metoo我们创建的索引名称
* CreateIndexResponse // 执行完毕之后返回的创建信息,里面包含es的分片副本等
* RequestOptions.DEFAULT // es访问方式,我们使用默认访问方式
*/
@GetMapping("/createIndex")
public String createIndex() {
// 创建索引请求
CreateIndexRequest createIndexRequest = new CreateIndexRequest("metoo");
try {
// 执行创建请求
CreateIndexResponse createIndexResponse = restHighLevelClient.indices().create(createIndexRequest, RequestOptions.DEFAULT);
// 响应
System.out.println(createIndexResponse);
return "create index success";
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException("create index defeated");
}
}
2、查询索引
@GetMapping("/getIndex")
public String getIndex() {
GetIndexRequest getIndexRequest = new GetIndexRequest("metoo");
try {
// 获取index对象
GetIndexResponse getIndexResponse = restHighLevelClient.indices().get(getIndexRequest, RequestOptions.DEFAULT);
// 打印索引对象
System.out.println(getIndexResponse);
// 检查索引是否存在
boolean exists = restHighLevelClient.indices().exists(getIndexRequest, RequestOptions.DEFAULT);
System.out.println("索引是否存在:" + exists);
return "find index success";
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException("find index defeated");
}
}
3、删除索引
@GetMapping("/deleteIndex")
public String deleteIndex() {
DeleteIndexRequest deleteIndexRequest = new DeleteIndexRequest("metoo");
try {
// 删除索引对象
AcknowledgedResponse delete = restHighLevelClient.indices().delete(deleteIndexRequest, RequestOptions.DEFAULT);
// 打印删除索引对象
System.out.println(delete);
return "delete index success";
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException("delete index defeated");
}
}
文档操作在ES,High客户端中为我们提供了多种方式,我们可以直接使用json字符串,也可以使用map的形式,es会自动为我们将map解析为json字符串,也可以使用es官方为我们提供的对象XContentBuilder。
1、 新建文档
/**
* 参数解析:
* IndexRequest //索引请求对象
* jsonMap // 这里我们使用Map封装文档参数,然后使用source为我们解析参数
*/
@GetMapping("/insertDocument")
public String insertDocument() {
//INDEX_NAME为我们的索引名
IndexRequest request = new IndexRequest(INDEX_NAME);
request.id("1");
Map<String, Object> jsonMap = new HashMap<>();
jsonMap.put("name", "张三");
jsonMap.put("gender", "男");
jsonMap.put("age", 18);
jsonMap.put("phone", "133111111111");
// 将参数解析存储在request里,然后调用highClient客户端发送到es
request.source(jsonMap);
try {
//获取响应
IndexResponse indexResponse = restHighLevelClient.index(request, RequestOptions.DEFAULT);
System.out.println(indexResponse);
return "create document success";
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException("create Document defeated");
}
}
2、根据文档id查询文档
@GetMapping("/findDocumentById")
public String findDocumentById() {
// 使用getRequest定义我们需要查询的索引与文档。这里使用GetRequest传递索引名,文档ID的重载方法
GetRequest getRequest = new GetRequest(INDEX_NAME, "1");
try {
//获取查询响应
GetResponse getResponse = restHighLevelClient.get(getRequest, RequestOptions.DEFAULT);
System.out.println(JSONObject.toJSON(getResponse));
// 从响应中获取响应参数
Map<String, Object> source = getResponse.getSource();
System.out.println(source.get("name"));
return "find document by id success";
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException("find document by id defeated");
}
}
3、根据文档id删除文档
@GetMapping("/deleteDocumentById")
public String deleteDocumentById() {
try {
// 使用deleteRequest对象,删除id为3的文档
DeleteRequest deleteRequest = new DeleteRequest(INDEX_NAME, "3");
// 请求方式为默认
DeleteResponse deleteResponse = restHighLevelClient.delete(
deleteRequest, RequestOptions.DEFAULT);
System.out.println(deleteResponse);
return "delete document by id success";
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException("delete document by id defeated");
}
}
4、根据文档id修改文档
@GetMapping("/updateDocumentById")
public String updateDocumentById() {
try {
// 使用updateRequest修改文档id为1的文档
UpdateRequest request = new UpdateRequest(INDEX_NAME, "1");
// 修改我们使用json字符串的方式
String jsonString = "{" +
"\"age\":\"30\"" +
"}";
// 标识参数为json字符串
request.doc(jsonString, XContentType.JSON);
// 请求方式为RequestOptions.DEFAULT默认
UpdateResponse updateResponse = restHighLevelClient.update(
request, RequestOptions.DEFAULT);
System.out.println(updateResponse);
return "update document by id success";
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("update document by id defeated");
}
}
5、组合查询
@GetMapping("/findDocumentByName")
public String findDocumentByName() {
try {
// 新建查询请求
SearchRequest searchRequest = new SearchRequest(INDEX_NAME);
// 使用bool组合查询
BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();
// 封装查询条件 使用matchQuery
// 查询name包含张的数据
MatchQueryBuilder matchQuery = QueryBuilders.matchQuery("name", "张");
// 查询age不为25岁的数据
MatchQueryBuilder matchNoteQuery = QueryBuilders.matchQuery("age", 25);
// 将我们定义的match和matchNot封装进bool
boolQueryBuilder.must(matchQuery);
boolQueryBuilder.mustNot(matchNoteQuery);
// 创建查询器,加入bool过滤器
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchSourceBuilder.query(boolQueryBuilder);
// 将查询器加入到request请求当中
searchRequest.source(searchSourceBuilder);
// 调用highClient的search方法传入request查询结果集
SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
SearchHits hits = searchResponse.getHits();
// 取出结果集中的hits
for (SearchHit hit : hits) {
String sourceAsString = hit.getSourceAsString();
System.out.println(sourceAsString);
}
return "bool query success";
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("bool query defeated");
}
}
其实上诉组合查询看似很复杂,创建了一堆对象。但其实我们通过http 请求分解看一下我们上述代码到底做的是什么操作,从下面http请求中我们可以看出,我们整个查询就是一个SearchRequest 查询对象,然后我们的highClient客户端无非就是将我们的查询关键字对象化,进行了封装。 所以各位小伙伴在操作highClient的时候,可以根据我们的rest请求去找到对应的对象。term、aggs亦是同理
http://112.74.48.31:9200/customer/_search
{
"query": { --对应对象SearchSourceBuilder(查询对象)
"bool": { --对应 BoolQueryBuilder 过滤器
"must": [ -- 对应MatchQueryBuilder查询条件对象
{ "match": { "name": "王二" } }
],
"must_not": [
{ "match": { "age": "18" } }
]
}
}
}
1、Bucket聚合查询,根据性别进行分组,求分组之后男女的平均年龄
@GetMapping("/findGenderGroupAndAgeAvg")
public String findGenderGroupAndAgeAvg() {
SearchRequest searchRequest = new SearchRequest();
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
//text类型不能用于索引或排序,必须转成keyword类型
TermsAggregationBuilder aggregation = AggregationBuilders.terms("metoo_gender")
.field("gender.keyword");
//avg_age 为子聚合名称,名称可随意
aggregation.subAggregation(AggregationBuilders.avg("metoo_avg")
.field("age"));
// 将聚合条件加入查询过滤器中
searchSourceBuilder.aggregation(aggregation);
// 将查询过滤加入request中
searchRequest.source(searchSourceBuilder);
SearchResponse searchResponse = null;
try {
// 嗲用search方法获取查询响应
searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException("aggs query defeated");
}
// 获取响应中的返回聚合参数列表
Aggregations aggregations = searchResponse.getAggregations();
// 以map形式获取聚合参数
Map<String, Aggregation> stringAggregationMap = aggregations.asMap();
// 获取返回参数中名字为metoo_gender的响应参数
Terms byCompanyAggregation = aggregations.get("metoo_gender");
// 从Terms响应参数中获取分组之后性别为女的桶数据
Terms.Bucket elasticBucket = byCompanyAggregation.getBucketByKey("女");
// 从桶中拿到子聚合中metoo_avg的平均年龄,由于我们请求使用的是AggregationBuilders.avg聚合所以取数据也使用Avg聚合
Avg averageAge = elasticBucket.getAggregations().get("metoo_avg");
// 取出
double avg = averageAge.getValue();
System.out.println("女性平均年龄:" + avg);
return "aggs query success";
}
2、Metric聚合,求所有人的总年龄
@GetMapping("/findTotalAge")
public String findTotalAge() {
SearchRequest searchRequest = new SearchRequest();
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
// 使用sum聚合,聚合名为metoo_gender,求和字段为age
SumAggregationBuilder sumAggregationBuilder = AggregationBuilders.sum("metoo_gender")
.field("age");
// 将sum聚合添加到查询器里面
searchSourceBuilder.aggregation(sumAggregationBuilder);
// 将查询器加入请求中
searchRequest.source(searchSourceBuilder);
SearchResponse searchResponse = null;
try {
// 执行search获取es响应
searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
} catch (IOException e) {
e.printStackTrace();
}
// 从响应中获取Aggregations聚合参数列表
Aggregations aggregations = searchResponse.getAggregations();
// 将聚合参数列表变为map
Map<String, Aggregation> stringAggregationMap = aggregations.asMap();
// 使用ParsedSum接收聚合参数。他是Aggregation的子类(Aggregation无法直接取出参数值,需要找到对应聚合查询下的参数接收)
ParsedSum metooGender = aggregations.get("metoo_gender");
// 取出值
double value = metooGender.getValue();
return "success ok";
}
在我们的聚合查询中,HighClient为我们提供了一系列的Api供我们使用,但是会存在一个问题,我们使用什么聚合方式就需要找到对应的接收方式。而ES官方文档描述又不够全面,只能自己去寻找对应API,像帅途这种半吊子英语的那真的是一件异常痛苦的事情。所以帅途这里为大家找到一个小技巧。根据Rest Api调用的aggs聚合参数类型,找到对应的请求类之后,我们可以使用aggregations.asMap();方法将他变为Map集合,在我们Debug的时候就能看见该对象对应的聚合返回值类型。然后直接使用该对象接收取值即可。
其实在目前版本ES HighClient客户端使用中,各位小伙伴只需要记住他是对我们Rest Api请求的封装,无论是响应也好,请求也好,这样任他千百个封装,我自巍然不动。而集成ES呢现在也非常方便,只需要两部,1、引入spring data es依赖,2、编写配置文件。即可使用,在使用这方面帅途个人觉得还是非常方便的。
最近帅途更新有点缓慢,除了最近工作比较忙之外,也遇到了一个性格很开朗,撩人心魄的女孩子。呸呸呸 我在说什么。我是那种被美色所左右的人吗?其实只是有点沉迷游戏,昨天还跟好基友跑到网吧玩了两盘游戏。负罪感有点强烈~
最后帅途跟大家说一个很有意思的事情,帅途的es系列虽然一直更新比较慢,但是一直都是惦记着的,并没有中途放弃的想法,但是最近发现了一个让帅途比较郁闷的事情。帅途从搭建开始,考虑到可能有些小伙伴操作不太方便。所以在自己的阿里云服务器es的配置中,开放域的配置是0.0.0.0,对所有人开放,并且开放域名访问。但是最近有些小可爱,比较有意思,把帅途的es服务数据变成了这样。
帅途的测试es真的值得了那么多钱吗,这有待商议。不过也给帅途提了个醒,各位小伙伴在配置es的时候,如果是商用自己搭建的千万不要配置0.0.0.0的开放域哟,一定要注意ES的安全。该项目已上传至GitHub拆箱即用,仓库地址:metoo-elasticsearch 最后希望大家看了帅途本篇文章,能都很快速又便捷的掌握最新版es的用法。
看到这儿了 赶快点赞关注一下把,点赞关注之后,私信帅途,有最新最全的Java学习资料送给您呢。