大家好我是迷途,一个在互联网行业,摸爬滚打的学子。热爱学习,热爱代码,热爱技术。热爱互联网的一切。再也不怕elasticsearch系列,帅途会慢慢由浅入深,为大家剖析一遍,各位大佬请放心,虽然这个系列帅途有时候更新的有点慢,但是绝对不会烂尾!如果你喜欢本系列的话,就快点赞关注收藏安排一波吧~
文章目录
前言
之前帅途用了大量的篇幅讲解了es中restapi的用法,所以本文不会用太多篇幅去讲解es的语法,如果不清楚的同学可以看看帅途之前的文章,es一些查询,进阶语法之类的都已经详细的描述过了。ES在7.X之后已经不在支持TransportClient进行连接了,本文主要讲解es新版本的连接方式,使用Java Rest Api连接es,
正文
es的几种集成方式
目前在官方文档中Es官方为我们提供了各种语言的集成方式。Java主要分为原生Api连接与Rest Api连接。目前官方推荐使用Rest Api进行集成。在Rest Api中又分为Low版本和High版本。High对Low版本进行了加强。做为帅途当然要使用High版本的啦~
Java High Level REST Client简介
High Rest Api 是基于Low Rest Api 封装的。他其实就是对我们使用Rest Api调用ES时在原本HTTP请求的基础之上,将我们的请求和响应封装成了对象。例如我们在HTTP请求中的聚合关键字aggs就被封装成了AggregationBuilders(聚合请求顶级父类对象,根据我们聚合请求的参数不同,有不同的子类)对象。所以大家在学习High语法的时候,看到大量的封装API,别怕,别头疼。帅途这里告诉大家一个小妙招:其实大部分Java代码中的语法跟咋门使用Rest Api访问es是一样的。我们根据我们Api中的关键字就可以找到大部分ES为我们封装的对象。另外,在我们每个Api的调用中,ES都为我们提供了两种调用方式,一种是同步,一种是异步,同步调用在请求之后会返回一个响应对象,也是由我们API封装接收,而异步调用则需要我们定义一个监听器,在收到响应或者错误是被通知。
spring boot集成ES环境搭建
-
1、引入Maven依赖
搭建spring boot环境帅途这里就不过多赘述了。不会的小伙伴可以自行查阅一下资料
<!--使用spring data es--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-elasticsearch</artifactId> <version>2.3.0.RELEASE</version> </dependency>
在我们引入es依赖的时候有一个小注意点。我们jar包中es的版本需要和我们安装的es版本对应,否则可能会出现Api无法调用等问题,在properties中控制es版本
<properties> <java.version>1.8</java.version> <!-- 自定义es版本依赖,需要和我们的ES版本一致否者可能会导致连接不上es--> <elasticsearch.version>7.3.2</elasticsearch.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操作索引
由于我们之前将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"); } }
RestHighLevelClient操作文档
文档操作在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" } } ] } } }
RestHighLevelClient聚合查询
-
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学习资料送给您呢。