《再也不怕elasticsearch》Spring Boot集成Elasticsearch

大家好我是迷途,一个在互联网行业,摸爬滚打的学子。热爱学习,热爱代码,热爱技术。热爱互联网的一切。再也不怕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学习资料送给您呢。

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值