相关推荐recommended
基于SpringBoot的ElasticSearch操作(超详细教程)
作者:mmseoamin日期:2023-12-13

一、ElasticSearch 简介

1、简介

ElasticSearch 是一个基于 Lucene 的搜索服务器。它提供了一个分布式多员工能力的全文搜索引擎,基于 RESTful web 接口。Elasticsearch 是用 Java 语言开发的,并作为 Apache 许可条款下的开放源码发布,是一种流行的企业级搜索引擎。

ElasticSearch 用于云计算中,能够达到实时搜索,稳定,可靠,快速,安装使用方便。

2、特性

分布式的文档存储引擎

分布式的搜索引擎和分析引擎

分布式,支持PB级数据

3、使用场景

搜索领域:如百度、谷歌,全文检索等。

门户网站:访问统计、文章点赞、留言评论等。

广告推广:记录员工行为数据、消费趋势、员工群体进行定制推广等。

信息采集:记录应用的埋点数据、访问日志数据等,方便大数据进行分析。

二、ElasticSearch 基础概念

1、ElaticSearch 和 DB 的关系

在 Elasticsearch 中,文档归属于一种类型 type,而这些类型存在于索引 index 中,我们可以列一些简单的不同点,来类比传统关系型数据库:

Relational DB -> Databases -> Tables -> Rows -> Columns

Elasticsearch -> Indices -> Types -> Documents -> Fields

Elasticsearch 集群可以包含多个索引 indices,每一个索引可以包含多个类型 types,每一个类型包含多个文档 documents,然后每个文档包含多个字段 Fields。而在 DB 中可以有多个数据库 Databases,每个库中可以有多张表 Tables,没个表中又包含多行Rows,每行包含多列Columns。

2、索引

索引基本概念(indices):

索引是含义相同属性的文档集合,是 ElasticSearch 的一个逻辑存储,可以理解为关系型数据库中的数据库,ElasticSearch 可以把索引数据存放到一台服务器上,也可以 sharding 后存到多台服务器上,每个索引有一个或多个分片,每个分片可以有多个副本。

索引类型(index_type):

索引可以定义一个或多个类型,文档必须属于一个类型。在 ElasticSearch 中,一个索引对象可以存储多个不同用途的对象,通过索引类型可以区分单个索引中的不同对象,可以理解为关系型数据库中的表。每个索引类型可以有不同的结构,但是不同的索引类型不能为相同的属性设置不同的类型。

3、文档

文档(document):

文档是可以被索引的基本数据单位。存储在 ElasticSearch 中的主要实体叫文档 document,可以理解为关系型数据库中表的一行记录。每个文档由多个字段构成,ElasticSearch 是一个非结构化的数据库,每个文档可以有不同的字段,并且有一个唯一的标识符。

4、映射

映射(mapping):

ElasticSearch 的 Mapping 非常类似于静态语言中的数据类型:声明一个变量为 int 类型的变量,以后这个变量都只能存储 int 类型的数据。同样的,一个 number 类型的 mapping 字段只能存储 number 类型的数据。

同语言的数据类型相比,Mapping 还有一些其他的含义,Mapping 不仅告诉 ElasticSearch 一个 Field 中是什么类型的值, 它还告诉 ElasticSearch 如何索引数据以及数据是否能被搜索到。

ElaticSearch 默认是动态创建索引和索引类型的 Mapping 的。这就相当于无需定义 Solr 中的 Schema,无需指定各个字段的索引规则就可以索引文件,很方便。但有时方便就代表着不灵活。比如,ElasticSearch 默认一个字段是要做分词的,但我们有时要搜索匹配整个字段却不行。如有统计工作要记录每个城市出现的次数。对于 name 字段,若记录 new york 文本,ElasticSearch 可能会把它拆分成 new 和 york 这两个词,分别计算这个两个单词的次数,而不是我们期望的 new york。

三、SpringBoot 项目引入 ElasticSearch 依赖

下面介绍下 SpringBoot 如何通过 elasticsearch-rest-high-level-client 工具操作 ElasticSearch,这里需要说一下,为什么没有使用 Spring 家族封装的 spring-data-elasticsearch。

主要原因是灵活性和更新速度,Spring 将 ElasticSearch 过度封装,让开发者很难跟 ES 的 DSL 查询语句进行关联。再者就是更新速度,ES 的更新速度是非常快,但是 spring-data-elasticsearch 更新速度比较缓慢。

由于上面两点,所以选择了官方推出的 Java 客户端 elasticsearch-rest-high-level-client,它的代码写法跟 DSL 语句很相似,懂 ES 查询的使用其上手很快。

【注意SpringBoot的版本-es的版本对应】

基于SpringBoot的ElasticSearch操作(超详细教程),第1张

1、Maven 引入相关依赖

  • lombok:lombok 工具依赖。
  • fastjson:用于将 JSON 转换对象的依赖。
  • spring-boot-starter-web: SpringBoot 的 Web 依赖。
  • elasticsearch:ElasticSearch:依赖,需要和 ES 版本保持一致。
  • elasticsearch-rest-high-level-client:用于操作 ES 的 Java 客户端。
    
    
        4.0.0
        com.example
        elasticsearch
        0.0.1-SNAPSHOT
        elasticsearch
        Demo project for Spring Boot
        
            1.8
            UTF-8
            UTF-8
            2.3.12.RELEASE
        
        
            
                org.springframework.boot
                spring-boot-starter-web
            
            
                org.springframework.boot
                spring-boot-starter-test
                test
                
                    
                        org.junit.vintage
                        junit-vintage-engine
                    
                
            
            
                org.projectlombok
                lombok
                true
            
            
            
                com.alibaba
                fastjson
                1.2.61
            
            
            
                org.elasticsearch.client
                elasticsearch-rest-high-level-client
                7.6.1
            
            
                org.elasticsearch
                elasticsearch
                7.6.1
            
        
        
            
                
                    org.springframework.boot
                    spring-boot-dependencies
                    ${spring-boot.version}
                    pom
                    import
                
            
        
        
            
                
                    org.apache.maven.plugins
                    maven-compiler-plugin
                    3.8.1
                    
                        1.8
                        1.8
                        UTF-8
                    
                
                
                    org.springframework.boot
                    spring-boot-maven-plugin
                    ${spring-boot.version}
                    
                        com.example.elasticsearch.ElasticsearchApplication
                        true
                    
                    
                        
                            repackage
                            
                                repackage
                            
                        
                    
                
            
        
    
    

    2、ElasticSearch 连接配置

    (1)、application.yml 配置文件

    为了方便更改连接 ES 的连接配置,所以我们将配置信息放置于 application.yml 中:

    server:
      port: 8080
      servlet:
        context-path: /search
    elasticsearch:
      schema: http
      address: 127.0.0.1:9200
      connectTimeout: 10000
      socketTimeout: 10000
      connectionRequestTimeout: 10000
      maxConnectNum: 100
      maxConnectPerRoute: 100
    myindex: testindex

    (2)、java 连接配置类

    这里需要写一个 Java 配置类读取 application 中的配置信息:

    package com.example.elasticsearch.demos.config;
    import org.apache.http.HttpHost;
    import org.elasticsearch.client.RestClient;
    import org.elasticsearch.client.RestClientBuilder;
    import org.elasticsearch.client.RestHighLevelClient;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import java.util.ArrayList;
    import java.util.List;
    /**
     * ElasticSearch 配置
     */
    @Configuration
    public class ElasticSearchConfig {
        /** 协议 */
        @Value("${elasticsearch.schema:http}")
        private String schema;
        /** 集群地址,如果有多个用“,”隔开 */
        @Value("${elasticsearch.address}")
        private String address;
        /** 连接超时时间 */
        @Value("${elasticsearch.connectTimeout:5000}")
        private int connectTimeout;
        /** Socket 连接超时时间 */
        @Value("${elasticsearch.socketTimeout:10000}")
        private int socketTimeout;
        /** 获取连接的超时时间 */
        @Value("${elasticsearch.connectionRequestTimeout:5000}")
        private int connectionRequestTimeout;
        /** 最大连接数 */
        @Value("${elasticsearch.maxConnectNum:100}")
        private int maxConnectNum;
        /** 最大路由连接数 */
        @Value("${elasticsearch.maxConnectPerRoute:100}")
        private int maxConnectPerRoute;
        @Bean
        public RestHighLevelClient restHighLevelClient() {
            // 拆分地址
            List hostLists = new ArrayList<>();
            String[] hostList = address.split(",");
            for (String addr : hostList) {
                String host = addr.split(":")[0];
                String port = addr.split(":")[1];
                hostLists.add(new HttpHost(host, Integer.parseInt(port), schema));
            }
            // 转换成 HttpHost 数组
            HttpHost[] httpHost = hostLists.toArray(new HttpHost[]{});
            // 构建连接对象
            RestClientBuilder builder = RestClient.builder(httpHost);
            // 异步连接延时配置
            builder.setRequestConfigCallback(requestConfigBuilder -> {
                requestConfigBuilder.setConnectTimeout(connectTimeout);
                requestConfigBuilder.setSocketTimeout(socketTimeout);
                requestConfigBuilder.setConnectionRequestTimeout(connectionRequestTimeout);
                return requestConfigBuilder;
            });
            // 异步连接数配置
            builder.setHttpClientConfigCallback(httpClientBuilder -> {
                httpClientBuilder.setMaxConnTotal(maxConnectNum);
                httpClientBuilder.setMaxConnPerRoute(maxConnectPerRoute);
                return httpClientBuilder;
            });
            return new RestHighLevelClient(builder);
        }
    }
    

    四、索引操作示例

    这里示例会指出通过Postman的 ​ ​Restful​​​ 工具操作与对应的 Java 代码操作的两个示例。

    1、Restful 操作示例

    创建索引

    创建名为 testindex 的索引与对应 Mapping。

    PUT http://localhost:9200/testindex
    {
      "mappings": {
        "doc": {
          "dynamic": true,
          "properties": {
            "name": {
              "type": "text",
              "fields": {
                "keyword": {
                  "type": "keyword"
                }
              }
            },
            "address": {
              "type": "text",
              "fields": {
                "keyword": {
                  "type": "keyword"
                }
              }
            },
            "remark": {
              "type": "text",
              "fields": {
                "keyword": {
                  "type": "keyword"
                }
              }
            },
            "age": {
              "type": "integer"
            },
            "salary": {
              "type": "float"
            },
            "birthDate": {
              "type": "date",
              "format": "yyyy-MM-dd"
            },
            "createTime": {
              "type": "date"
            }
          }
        }
      }
    }

    删除索引

    删除 mydlq-user 索引。

    DELETE http://localhost:9200/testindex

    2、Java 代码示例

    package com.example.elasticsearch.demos.web.service.base;
    import lombok.extern.slf4j.Slf4j;
    import org.elasticsearch.action.admin.indices.create.CreateIndexRequest;
    import org.elasticsearch.action.admin.indices.create.CreateIndexResponse;
    import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;
    import org.elasticsearch.action.admin.indices.get.GetIndexRequest;
    import org.elasticsearch.action.support.master.AcknowledgedResponse;
    import org.elasticsearch.client.RequestOptions;
    import org.elasticsearch.client.RestHighLevelClient;
    import org.elasticsearch.common.settings.Settings;
    import org.elasticsearch.common.xcontent.XContentBuilder;
    import org.elasticsearch.common.xcontent.XContentFactory;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    import java.io.IOException;
    /**
     * 索引操作
     */
    @Slf4j
    @Service
    public class IndexService {
        @Autowired
        private RestHighLevelClient restHighLevelClient;
        /**
         * 验证索引是否存在
         */
        public Object existsIndex(String indexName) {
            Object result = "";
            try {
                // 获取索引请求
                GetIndexRequest request = new GetIndexRequest();
                // 设置要查询的索引名称
                request.indices(indexName);
                // 执行请求,验证索引是否存在
                boolean isExist = restHighLevelClient.indices().exists(request, RequestOptions.DEFAULT);
                log.info("是否存在:{}", isExist);
                // 根据具体业务逻辑返回不同结果,这里为了方便直接将结果返回
                result = isExist;
            } catch (IOException e) {
                log.error("", e);
            }
            return result;
        }
        /**
         * 创建索引
         */
        public Object createIndex(String indexName) {
            Object result = "";
            try {
                // 创建 Mapping
                XContentBuilder mapping = XContentFactory.jsonBuilder()
                    .startObject()
                        .field("dynamic", true)
                        .startObject("properties")
                            .startObject("name")
                                .field("type","text")
                                .startObject("fields")
                                    .startObject("keyword")
                                        .field("type","keyword")
                                    .endObject()
                                .endObject()
                            .endObject()
                            .startObject("address")
                                .field("type","text")
                                .startObject("fields")
                                    .startObject("keyword")
                                        .field("type","keyword")
                                    .endObject()
                                .endObject()
                            .endObject()
                            .startObject("remark")
                                .field("type","text")
                                .startObject("fields")
                                    .startObject("keyword")
                                        .field("type","keyword")
                                    .endObject()
                                .endObject()
                            .endObject()
                            .startObject("age")
                                .field("type","integer")
                            .endObject()
                            .startObject("salary")
                                .field("type","float")
                            .endObject()
                            .startObject("birthDate")
                                .field("type","date")
                                .field("format", "yyyy-MM-dd")
                            .endObject()
                            .startObject("createTime")
                                .field("type","date")
                            .endObject()
                        .endObject()
                    .endObject();
                // 创建索引配置信息,配置
                Settings settings = Settings.builder()
                        .put("index.number_of_shards", 1)
                        .put("index.number_of_replicas", 0)
                        .build();
                // 新建创建索引请求对象,然后设置索引类型(ES 7.0 将不存在索引类型)和 mapping 与 index 配置
                CreateIndexRequest request = new CreateIndexRequest(indexName, settings);
                request.mapping("doc", mapping);
                // RestHighLevelClient 执行创建索引
                CreateIndexResponse createIndexResponse = restHighLevelClient.indices().create(request, RequestOptions.DEFAULT);
                // 判断是否创建成功
                boolean isCreated = createIndexResponse.isAcknowledged();
                log.info("是否创建成功:{}", isCreated);
                // 根据具体业务逻辑返回不同结果,这里为了方便直接将结果返回
                result = isCreated;
            } catch (IOException e) {
                log.error("", e);
            }
            return result;
        }
        /**
         * 删除索引
         */
        public Object deleteIndex(String indexName) {
            Object result = "";
            try {
                // 新建删除索引请求对象
                DeleteIndexRequest request = new DeleteIndexRequest(indexName);
                // 执行删除索引
                AcknowledgedResponse acknowledgedResponse = restHighLevelClient.indices().delete(request, RequestOptions.DEFAULT);
                // 判断是否删除成功
                boolean siDeleted = acknowledgedResponse.isAcknowledged();
                log.info("是否删除成功:{}", siDeleted);
                // 根据具体业务逻辑返回不同结果,这里为了方便直接将结果返回
                result = siDeleted;
            } catch (IOException e) {
                log.error("", e);
            }
            return result;
        }
    }
    

    五、文档操作示例

    1、Restful 操作示例

    增加文档信息

    在索引 mydlq-user 中增加一条文档信息。

    POST http://localhost:9200/testindex/doc
    {
        "address": "北京市",
        "age": 29,
        "birthDate": "1990-01-10",
        "createTime": 1579530727699,
        "name": "张三",
        "remark": "来自北京市的张先生",
        "salary": 100
    }
    //返回
    {
        "_index": "testindex",
        "_type": "doc",
        "_id": "hZo5_4oBFE0BmNy_GMUN", //这个是插入生成的随机id
        "_version": 1,
        "result": "created",
        "_shards": {
            "total": 1,
            "successful": 1,
            "failed": 0
        },
        "_seq_no": 29,
        "_primary_term": 3
    }

    获取文档信息

    获取 testindex的索引 id=hZo5_4oBFE0BmNy_GMUN 的文档信息。

    GET http://localhost:9200/testindex/doc/hZo5_4oBFE0BmNy_GMUN
    //返回
    {
        "_index": "testindex",
        "_type": "doc",
        "_id": "hZo5_4oBFE0BmNy_GMUN",
        "_version": 1,
        "_seq_no": 29,
        "_primary_term": 3,
        "found": true,
        "_source": {
            "address": "北京市",
            "age": 29,
            "birthDate": "1990-01-10",
            "createTime": 1579530727699,
            "name": "张三",
            "remark": "来自北京市的张先生",
            "salary": 100
        }
    }

    更新文档信息

    更新之前创建的 id=hZo5_4oBFE0BmNy_GMUN 的文档信息。

    PUT http://localhost:9200/testindex/doc/hZo5_4oBFE0BmNy_GMUN
    //请求
    {
        "address": "北京市",
        "age": 29,
        "birthDate": "1990-01-10",
        "createTime": 1579530727699,
        "name": "张三(改名字)",
        "remark": "来自北京市的张先生",
        "salary": 100
    }

    删除文档信息

    删除之前创建的 id=hZo5_4oBFE0BmNy_GMUN 的文档信息。

    DELETE http://localhost:9200/testindex/doc/hZo5_4oBFE0BmNy_GMUN

    2、Java 代码示例

    package com.example.elasticsearch.demos.web.service.base;
    import com.alibaba.fastjson.JSON;
    import com.example.elasticsearch.demos.web.model.dto.DocDto;
    import com.example.elasticsearch.demos.web.model.entity.UserInfo;
    import lombok.extern.slf4j.Slf4j;
    import org.elasticsearch.action.delete.DeleteRequest;
    import org.elasticsearch.action.delete.DeleteResponse;
    import org.elasticsearch.action.get.GetRequest;
    import org.elasticsearch.action.get.GetResponse;
    import org.elasticsearch.action.index.IndexRequest;
    import org.elasticsearch.action.index.IndexResponse;
    import org.elasticsearch.action.update.UpdateRequest;
    import org.elasticsearch.action.update.UpdateResponse;
    import org.elasticsearch.client.RequestOptions;
    import org.elasticsearch.client.RestHighLevelClient;
    import org.elasticsearch.common.xcontent.XContentType;
    import org.elasticsearch.search.fetch.subphase.FetchSourceContext;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    import java.io.IOException;
    import java.util.Date;
    /**
     * 文档操作
     */
    @Slf4j
    @Service
    public class DocumentService {
        @Autowired
        private RestHighLevelClient restHighLevelClient;
        public Object existsDocument(DocDto docDto) {
            Object result = "";
            try {
                // 获取请求对象
                GetRequest getRequest = new GetRequest(docDto.getIndexName(), docDto.getDocId());
                // 是否获取源码内容
                getRequest.fetchSourceContext(new FetchSourceContext(false));
                // 执行请求,验证文档是否存在
                boolean isExist = restHighLevelClient.exists(getRequest, RequestOptions.DEFAULT);
                log.info("文档是否存在:{}", isExist);
                // 根据具体业务逻辑返回不同结果,这里为了方便直接将结果返回
                result = isExist;
            } catch (IOException e) {
                log.error("", e);
            }
            return result;
        }
        public Object getDocument(DocDto docDto) {
            Object result = "";
            try {
                // 获取请求对象
                GetRequest getRequest = new GetRequest(docDto.getIndexName(), docDto.getDocId());
                // 获取文档信息
                GetResponse getResponse = restHighLevelClient.get(getRequest, RequestOptions.DEFAULT);
                // 将 JSON 转换成对象
                if (getResponse.isExists()) {
                    UserInfo userInfo = JSON.parseObject(getResponse.getSourceAsBytes(), UserInfo.class);
                    log.info("用户信息:{}", userInfo);
                }
                // 根据具体业务逻辑返回不同结果,这里为了方便直接将结果返回
                result = getResponse;
            } catch (IOException e) {
                log.error("", e);
            }
            return result;
        }
        public Object addDocument(DocDto docDto) {
            Object result = "";
            try {
                // 创建索引请求对象
                IndexRequest indexRequest = new IndexRequest(docDto.getIndexName());
                // 创建用户信息
                UserInfo userInfo = new UserInfo();
                userInfo.setName(docDto.getName());
                userInfo.setAge(docDto.getAge());
                userInfo.setSalary(docDto.getSalary());
                userInfo.setAddress(docDto.getAddress());
                userInfo.setRemark(docDto.getRemark());
                userInfo.setCreateTime(new Date());
                userInfo.setBirthDate(docDto.getBirthDate());
                // 将对象转换为 byte 数组
                byte[] json = JSON.toJSONBytes(userInfo);
                // 设置文档内容
                indexRequest.source(json, XContentType.JSON);
                // 执行增加文档
                IndexResponse response = restHighLevelClient.index(indexRequest, RequestOptions.DEFAULT);
                log.info("创建状态:{}", response.status());
                // 根据具体业务逻辑返回不同结果,这里为了方便直接将结果返回
                result = response;
            } catch (Exception e) {
                log.error("", e);
            }
            return result;
        }
        public Object updateDocument(DocDto docDto) {
            Object result = "";
            try {
                // 创建索引请求对象
                UpdateRequest updateRequest = new UpdateRequest(docDto.getIndexName(), docDto.getDocId());
    //            UpdateRequest updateRequest = new UpdateRequest(docDto.getIndexName(), "doc", docDto.getDocId());
                // 设置用户更新信息
                UserInfo userInfo = new UserInfo();
                userInfo.setSalary(docDto.getSalary());
                userInfo.setAddress(docDto.getAddress());
                // 将对象转换为 byte 数组
                byte[] json = JSON.toJSONBytes(userInfo);
                // 设置更新文档内容
                updateRequest.doc(json, XContentType.JSON);
                // 执行更新文档
                UpdateResponse response = restHighLevelClient.update(updateRequest, RequestOptions.DEFAULT);
                log.info("创建状态:{}", response.status());
                // 根据具体业务逻辑返回不同结果,这里为了方便直接将结果返回
                result = response;
            } catch (Exception e) {
                log.error("", e);
            }
            return result;
        }
        public Object deleteDocument(DocDto docDto) {
            Object result = "";
            try {
                // 创建删除请求对象
                DeleteRequest deleteRequest = new DeleteRequest(docDto.getIndexName(), docDto.getDocId());
                // 执行删除文档
                DeleteResponse response = restHighLevelClient.delete(deleteRequest, RequestOptions.DEFAULT);
                log.info("删除状态:{}", response.status());
                // 根据具体业务逻辑返回不同结果,这里为了方便直接将结果返回
                result = response;
            } catch (IOException e) {
                log.error("", e);
            }
            return result;
        }
    }
    

    六、插入初始化数据

    执行查询示例前,先往索引中插入一批数据:

    1、单条插入

    POST http://localhost:9200/testindex/doc
    //请求
    {
        "name": "零零",
        "address": "北京市丰台区",
        "remark": "低层员工",
        "age": 29,
        "salary": 3000,
        "birthDate": "1990-11-11",
        "createTime": "2019-11-11T08:18:00.000Z"
    }

    2、批量插入

    基于SpringBoot的ElasticSearch操作(超详细教程),第2张

    POST http://localhost:9200/_bulk
    //header
    Content-Type: application/json
    //body
    {"index":{"_index":"testindex","_type":"doc"}}
    {"name":"刘一","address":"北京市丰台区","remark":"低层员工","age":30,"salary":3000,"birthDate":"1989-11-11","createTime":"2019-03-15T08:18:00.000Z"} 
    {"index":{"_index":"testindex","_type":"doc"}}
    {"name":"陈二","address":"北京市昌平区","remark":"中层员工","age":27,"salary":7900,"birthDate":"1992-01-25","createTime":"2019-11-08T11:15:00.000Z"} 
    {"index":{"_index":"testindex","_type":"doc"}} 
    {"name":"张三","address":"北京市房山区","remark":"中层员工","age":28,"salary":8800,"birthDate":"1991-10-05","createTime":"2019-07-22T13:22:00.000Z"} 
    {"index":{"_index":"testindex","_type":"doc"}}
    {"name":"李四","address":"北京市大兴区","remark":"高层员工","age":26,"salary":9000,"birthDate":"1993-08-18","createTime":"2019-10-17T15:00:00.000Z"} 
    {"index":{"_index":"testindex","_type":"doc"}}
    {"name":"王五","address":"北京市密云区","remark":"低层员工","age":31,"salary":4800,"birthDate":"1988-07-20","createTime":"2019-05-29T09:00:00.000Z"} 
    {"index":{"_index":"testindex","_type":"doc"}}
    {"name":"赵六","address":"北京市通州区","remark":"中层员工","age":32,"salary":6500,"birthDate":"1987-06-02","createTime":"2019-12-10T18:00:00.000Z"} 
    {"index":{"_index":"testindex","_type":"doc"}}
    {"name":"孙七","address":"北京市朝阳区","remark":"中层员工","age":33,"salary":7000,"birthDate":"1986-04-15","createTime":"2019-06-06T13:00:00.000Z"} 
    {"index":{"_index":"testindex","_type":"doc"}}
    {"name":"周八","address":"北京市西城区","remark":"低层员工","age":32,"salary":5000,"birthDate":"1987-09-26","createTime":"2019-01-26T14:00:00.000Z"} 
    {"index":{"_index":"testindex","_type":"doc"}}
    {"name":"吴九","address":"北京市海淀区","remark":"高层员工","age":30,"salary":11000,"birthDate":"1989-11-25","createTime":"2019-09-07T13:34:00.000Z"} 
    {"index":{"_index":"testindex","_type":"doc"}}
    {"name":"郑十","address":"北京市东城区","remark":"低层员工","age":29,"salary":5000,"birthDate":"1990-12-25","createTime":"2019-03-06T12:08:00.000Z"} 
    {"index":{"_index":"testindex","_type":"doc"}}
    {"name":"萧十一","address":"北京市平谷区","remark":"低层员工","age":29,"salary":3300,"birthDate":"1990-11-11","createTime":"2019-03-10T08:17:00.000Z"} 
    {"index":{"_index":"testindex","_type":"doc"}} 
    {"name":"曹十二","address":"北京市怀柔区","remark":"中层员工","age":27,"salary":6800,"birthDate":"1992-01-25","createTime":"2019-12-03T11:09:00.000Z"} 
    {"index":{"_index":"testindex","_type":"doc"}}
    {"name":"吴十三","address":"北京市延庆区","remark":"中层员工","age":25,"salary":7000,"birthDate":"1994-10-05","createTime":"2019-07-27T14:22:00.000Z"} 
    {"index":{"_index":"testindex","_type":"doc"}}
    {"name":"冯十四","address":"北京市密云区","remark":"低层员工","age":25,"salary":3000,"birthDate":"1994-08-18","createTime":"2019-04-22T15:00:00.000Z"} 
    {"index":{"_index":"testindex","_type":"doc"}}
    {"name":"蒋十五","address":"北京市通州区","remark":"低层员工","age":31,"salary":2800,"birthDate":"1988-07-20","createTime":"2019-06-13T10:00:00.000Z"} 
    {"index":{"_index":"testindex","_type":"doc"}}
    {"name":"苗十六","address":"北京市门头沟区","remark":"高层员工","age":32,"salary":11500,"birthDate":"1987-06-02","createTime":"2019-11-11T18:00:00.000Z"} 
    {"index":{"_index":"testindex","_type":"doc"}}
    {"name":"鲁十七","address":"北京市石景山区","remark":"高员工","age":33,"salary":9500,"birthDate":"1986-04-15","createTime":"2019-06-06T14:00:00.000Z"} 
    {"index":{"_index":"testindex","_type":"doc"}}
    {"name":"沈十八","address":"北京市朝阳区","remark":"中层员工","age":31,"salary":8300,"birthDate":"1988-09-26","createTime":"2019-09-25T14:00:00.000Z"} 
    {"index":{"_index":"testindex","_type":"doc"}}
    {"name":"吕十九","address":"北京市西城区","remark":"低层员工","age":31,"salary":4500,"birthDate":"1988-11-25","createTime":"2019-09-22T13:34:00.000Z"} 
    {"index":{"_index":"testindex","_type":"doc"}}
    {"name":"丁二十","address":"北京市东城区","remark":"低层员工","age":33,"salary":2100,"birthDate":"1986-12-25","createTime":"2019-03-07T12:08:00.000Z"}
    

    3、查询数据

    插入完成后再查询数据,查看之前插入的数据是否存在:

    GET http://localhost:9200/testindex/_search
    //返回
    {
        "took": 6,
        "timed_out": false,
        "_shards": {
            "total": 1,
            "successful": 1,
            "skipped": 0,
            "failed": 0
        },
        "hits": {
            "total": {
                "value": 2,
                "relation": "eq"
            },
            "max_score": 2.302585,
            "hits": [
                {
                    "_index": "testindex",
                    "_type": "doc",
                    "_id": "3iDh-IoByPOFA_QWinlo",
                    "_score": 2.302585,
                    "_source": {
                        "name": "赵六",
                        "address": "北京市通州区",
                        "remark": "中层员工",
                        "age": 32,
                        "salary": 6500,
                        "birthDate": "1987-06-02",
                        "createTime": "2019-12-10T18:00:00.000Z"
                    }
                },
                {
                    "_index": "testindex",
                    "_type": "doc",
                    "_id": "5yDh-IoByPOFA_QWinlo",
                    "_score": 2.302585,
                    "_source": {
                        "name": "蒋十五",
                        "address": "北京市通州区",
                        "remark": "低层员工",
                        "age": 31,
                        "salary": 2800,
                        "birthDate": "1988-07-20",
                        "createTime": "2019-06-13T10:00:00.000Z"
                    }
                }
                ...
            ]
        }
    }

    七、查询操作示例

    1、精确查询(term)

    (1)、Restful 操作示例

    精确查询

    精确查询,查询地址为 北京市通州区 的人员信息:

    查询条件不会进行分词,但是查询内容可能会分词,导致查询不到。之前在创建索引时设置 Mapping 中 address 字段存在 keyword 字段是专门用于不分词查询的子字段。

    GET http://localhost:9200/testindex/_search
    //请求
    {
      "query": {
        "term": {
          "address.keyword": {
            "value": "北京市通州区"
          }
        }
      }
    }
    //返回
    {
        "took": 6,
        "timed_out": false,
        "_shards": {
            "total": 1,
            "successful": 1,
            "skipped": 0,
            "failed": 0
        },
        "hits": {
            "total": {
                "value": 2,
                "relation": "eq"
            },
            "max_score": 2.302585,
            "hits": [
                {
                    "_index": "testindex",
                    "_type": "doc",
                    "_id": "3iDh-IoByPOFA_QWinlo",
                    "_score": 2.302585,
                    "_source": {
                        "name": "赵六",
                        "address": "北京市通州区",
                        "remark": "中层员工",
                        "age": 32,
                        "salary": 6500,
                        "birthDate": "1987-06-02",
                        "createTime": "2019-12-10T18:00:00.000Z"
                    }
                },
                {
                    "_index": "testindex",
                    "_type": "doc",
                    "_id": "5yDh-IoByPOFA_QWinlo",
                    "_score": 2.302585,
                    "_source": {
                        "name": "蒋十五",
                        "address": "北京市通州区",
                        "remark": "低层员工",
                        "age": 31,
                        "salary": 2800,
                        "birthDate": "1988-07-20",
                        "createTime": "2019-06-13T10:00:00.000Z"
                    }
                }
                ...
            ]
        }
    }
    精确查询-多内容查询

    精确查询,查询地址为 北京市丰台区、北京市昌平区 或 北京市大兴区 的人员信息:

     

    GET http://localhost:9200/testindex/_search
    //请求
    {
      "query": {
        "terms": {
          "address.keyword": [
            "北京市丰台区",
            "北京市昌平区",
            "北京市大兴区"
          ]
        }
      }
    }

    (2)、Java 代码示例 

    package com.example.elasticsearch.demos.web.service.query;
    import com.alibaba.fastjson.JSON;
    import com.example.elasticsearch.demos.web.model.dto.TermsQueryDto;
    import com.example.elasticsearch.demos.web.model.entity.UserInfo;
    import lombok.extern.slf4j.Slf4j;
    import org.elasticsearch.action.search.SearchRequest;
    import org.elasticsearch.action.search.SearchResponse;
    import org.elasticsearch.client.RequestOptions;
    import org.elasticsearch.client.RestHighLevelClient;
    import org.elasticsearch.index.query.QueryBuilders;
    import org.elasticsearch.rest.RestStatus;
    import org.elasticsearch.search.SearchHit;
    import org.elasticsearch.search.SearchHits;
    import org.elasticsearch.search.builder.SearchSourceBuilder;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    import java.io.IOException;
    import java.util.Arrays;
    /**
     * 精确查询
     */
    @Slf4j
    @Service
    public class TermQueryService {
        @Autowired
        private RestHighLevelClient restHighLevelClient;
        /**
         * 精确查询(查询条件不会进行分词,但是查询内容可能会分词,导致查询不到)
         * @param queryDto
         */
        public Object termQuery(TermsQueryDto queryDto) {
            Object result = "";
            try {
                // 构建查询条件(注意:termQuery 支持多种格式查询,如 boolean、int、double、string 等,这里使用的是 string 的查询)
                SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
                searchSourceBuilder.query(QueryBuilders.termQuery(queryDto.getKey() +  ".keyword", queryDto.getValue()));
                // 创建查询请求对象,将查询对象配置到其中
                SearchRequest searchRequest = new SearchRequest(queryDto.getIndexName());
                searchRequest.source(searchSourceBuilder);
                // 执行查询,然后处理响应结果
                SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
                // 根据状态和数据条数验证是否返回了数据
                if (RestStatus.OK.equals(searchResponse.status()) && searchResponse.getHits().getTotalHits().value > 0) {
                    SearchHits hits = searchResponse.getHits();
                    for (SearchHit hit : hits) {
                        // 将 JSON 转换成对象
                        UserInfo userInfo = JSON.parseObject(hit.getSourceAsString(), UserInfo.class);
                        // 输出查询信息
                        log.info(userInfo.toString());
                    }
                }
                result = searchResponse.getHits();
            } catch (IOException e) {
                log.error("", e);
            }
            return result;
        }
        /**
         * 多个内容在一个字段中进行查询
         * @param queryDto
         */
        public Object termsQuery(TermsQueryDto queryDto) {
            Object result = "";
            try {
                // 构建查询条件(注意:termsQuery 支持多种格式查询,如 boolean、int、double、string 等,这里使用的是 string 的查询)
                SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
                searchSourceBuilder.query(QueryBuilders.termsQuery(queryDto.getKey() +  ".keyword", queryDto.getValues()));
                // 创建查询请求对象,将查询对象配置到其中
                SearchRequest searchRequest = new SearchRequest(queryDto.getIndexName());
                searchRequest.source(searchSourceBuilder);
                // 执行查询,然后处理响应结果
                SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
                // 根据状态和数据条数验证是否返回了数据
                if (RestStatus.OK.equals(searchResponse.status()) && searchResponse.getHits().getTotalHits().value > 0) {
                    SearchHits hits = searchResponse.getHits();
                    for (SearchHit hit : hits) {
                        // 将 JSON 转换成对象
                        UserInfo userInfo = JSON.parseObject(hit.getSourceAsString(), UserInfo.class);
                        // 输出查询信息
                        log.info(userInfo.toString());
                    }
                }
                result = searchResponse.getHits();
            } catch (IOException e) {
                log.error("", e);
            }
            return result;
        }
    }
    

    2、匹配查询(match)

    (1)、Restful 操作示例

    匹配查询全部数据与分页

    匹配查询符合条件的所有数据,并且设置以 salary 字段升序排序,并设置分页:

    GET http://localhost:9200/testindex/_search
    //请求
    {
      "query": {
        "match_all": {}
      },
      "from": 0,
      "size": 10,
      "sort": [
        {
          "salary": {
            "order": "asc"
          }
        }
      ]
    }
    匹配查询数据

    匹配查询地址为 通州区 的数据:

    GET http://localhost:9200/testindex/_search
    //请求
    {
      "query": {
        "match": {
          "address": "通州区"
        }
      }
    }
     词语匹配查询

    词语匹配进行查询,匹配 address 中为 北京市通州区 的员工信息:

    GET http://localhost:9200/testindex/_search
    //请求
    {
      "query": {
        "match_phrase": {
          "address": "北京市通州区"
        }
      }
    }
    内容多字段查询

    查询在字段 address、remark 中存在 北京 内容的员工信息:

    GET http://localhost:9200/testindex/_search
    //请求
    {
      "query": {
        "multi_match": {
          "query": "北京",
          "fields": ["address","remark"]
        }
      }
    }

     (2)、Java 代码示例

    package com.example.elasticsearch.demos.web.service.query;
    import com.alibaba.fastjson.JSON;
    import com.example.elasticsearch.demos.web.model.dto.MatchQueryDto;
    import com.example.elasticsearch.demos.web.model.entity.UserInfo;
    import lombok.extern.slf4j.Slf4j;
    import org.elasticsearch.action.search.SearchRequest;
    import org.elasticsearch.action.search.SearchResponse;
    import org.elasticsearch.client.RequestOptions;
    import org.elasticsearch.client.RestHighLevelClient;
    import org.elasticsearch.index.query.MatchAllQueryBuilder;
    import org.elasticsearch.index.query.QueryBuilders;
    import org.elasticsearch.rest.RestStatus;
    import org.elasticsearch.search.SearchHit;
    import org.elasticsearch.search.SearchHits;
    import org.elasticsearch.search.builder.SearchSourceBuilder;
    import org.elasticsearch.search.sort.SortOrder;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    import java.io.IOException;
    /**
     * 匹配查询
     */
    @Slf4j
    @Service
    public class MatchQueryService {
        @Autowired
        private RestHighLevelClient restHighLevelClient;
        /**
         * 匹配查询符合条件的所有数据,并设置分页
         * @param queryDto
         */
        public Object matchAllQuery(MatchQueryDto queryDto) {
            Object result = "";
            try {
                // 构建查询条件
                MatchAllQueryBuilder matchAllQueryBuilder = QueryBuilders.matchAllQuery();
                // 创建查询源构造器
                SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
                searchSourceBuilder.query(matchAllQueryBuilder);
                // 设置分页
                searchSourceBuilder.from((queryDto.getRows() - 1) * queryDto.getSize());
                searchSourceBuilder.size(queryDto.getSize());
                // 设置排序
                searchSourceBuilder.sort("salary", SortOrder.ASC);
                // 创建查询请求对象,将查询对象配置到其中
                SearchRequest searchRequest = new SearchRequest(queryDto.getIndexName());
                searchRequest.source(searchSourceBuilder);
                // 执行查询,然后处理响应结果
                SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
                // 根据状态和数据条数验证是否返回了数据
                if (RestStatus.OK.equals(searchResponse.status()) && searchResponse.getHits().getTotalHits().value > 0) {
                    SearchHits hits = searchResponse.getHits();
                    for (SearchHit hit : hits) {
                        // 将 JSON 转换成对象
                        UserInfo userInfo = JSON.parseObject(hit.getSourceAsString(), UserInfo.class);
                        // 输出查询信息
                        log.info(userInfo.toString());
                    }
                }
                result = searchResponse.getHits();
            } catch (IOException e) {
                log.error("", e);
            }
            return result;
        }
        /**
         * 匹配查询数据-or的方式
         * @param queryDto
         */
        public Object matchQuery(MatchQueryDto queryDto) {
            Object result = "";
            try {
                // 构建查询条件
                SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
                searchSourceBuilder.query(QueryBuilders.matchQuery(queryDto.getKey(), queryDto.getValue()));
    //            searchSourceBuilder.query(QueryBuilders.matchQuery("address", "通州区"));
                // 创建查询请求对象,将查询对象配置到其中
                SearchRequest searchRequest = new SearchRequest(queryDto.getIndexName());
                searchRequest.source(searchSourceBuilder);
                // 执行查询,然后处理响应结果
                SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
                // 根据状态和数据条数验证是否返回了数据
                if (RestStatus.OK.equals(searchResponse.status()) && searchResponse.getHits().getTotalHits().value > 0) {
                    SearchHits hits = searchResponse.getHits();
                    for (SearchHit hit : hits) {
                        // 将 JSON 转换成对象
                        UserInfo userInfo = JSON.parseObject(hit.getSourceAsString(), UserInfo.class);
                        // 输出查询信息
                        log.info(userInfo.toString());
                    }
                }
                result = searchResponse.getHits();
            } catch (IOException e) {
                log.error("", e);
            }
            return result;
        }
        /**
         * 词语匹配查询
         * @param queryDto
         */
        public Object matchPhraseQuery(MatchQueryDto queryDto) {
            Object result = "";
            try {
                // 构建查询条件
                SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
                searchSourceBuilder.query(QueryBuilders.matchPhraseQuery(queryDto.getKey(), queryDto.getValue()));
    //            searchSourceBuilder.query(QueryBuilders.matchPhraseQuery("address", "北京市通州区"));
                // 创建查询请求对象,将查询对象配置到其中
                SearchRequest searchRequest = new SearchRequest(queryDto.getIndexName());
                searchRequest.source(searchSourceBuilder);
                // 执行查询,然后处理响应结果
                SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
                // 根据状态和数据条数验证是否返回了数据
                if (RestStatus.OK.equals(searchResponse.status()) && searchResponse.getHits().getTotalHits().value > 0) {
                    SearchHits hits = searchResponse.getHits();
                    for (SearchHit hit : hits) {
                        // 将 JSON 转换成对象
                        UserInfo userInfo = JSON.parseObject(hit.getSourceAsString(), UserInfo.class);
                        // 输出查询信息
                        log.info(userInfo.toString());
                    }
                }
                result = searchResponse.getHits();
            } catch (IOException e) {
                log.error("", e);
            }
            return result;
        }
        /**
         * 内容在多字段中进行查询
         * @param queryDto
         */
        public Object matchMultiQuery(MatchQueryDto queryDto) {
            Object result = "";
            try {
                // 构建查询条件
                SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
                searchSourceBuilder.query(QueryBuilders.multiMatchQuery(queryDto.getKey(), queryDto.getValues()));
    //            searchSourceBuilder.query(QueryBuilders.multiMatchQuery("北京市", "address", "remark"));
                // 创建查询请求对象,将查询对象配置到其中
                SearchRequest searchRequest = new SearchRequest(queryDto.getIndexName());
                searchRequest.source(searchSourceBuilder);
                // 执行查询,然后处理响应结果
                SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
                // 根据状态和数据条数验证是否返回了数据
                if (RestStatus.OK.equals(searchResponse.status()) && searchResponse.getHits().getTotalHits().value > 0) {
                    SearchHits hits = searchResponse.getHits();
                    for (SearchHit hit : hits) {
                        // 将 JSON 转换成对象
                        UserInfo userInfo = JSON.parseObject(hit.getSourceAsString(), UserInfo.class);
                        // 输出查询信息
                        log.info(userInfo.toString());
                    }
                }
                result = searchResponse.getHits();
            } catch (IOException e) {
                log.error("", e);
            }
            return result;
        }
    }
    

    3、模糊查询(fuzzy)

    (1)、Restful 操作示例

    模糊查询所有以 三 结尾的姓名
    GET http://localhost:9200/testindex/_search
    //请求
    {
      "query": {
        "fuzzy": {
          "name": "三"
        }
      }
    }

     (2)、Java 代码示例

    package com.example.elasticsearch.demos.web.service.query;
    import com.alibaba.fastjson.JSON;
    import com.example.elasticsearch.demos.web.model.dto.MatchQueryDto;
    import com.example.elasticsearch.demos.web.model.entity.UserInfo;
    import lombok.extern.slf4j.Slf4j;
    import org.elasticsearch.action.search.SearchRequest;
    import org.elasticsearch.action.search.SearchResponse;
    import org.elasticsearch.client.RequestOptions;
    import org.elasticsearch.client.RestHighLevelClient;
    import org.elasticsearch.common.unit.Fuzziness;
    import org.elasticsearch.index.query.QueryBuilders;
    import org.elasticsearch.rest.RestStatus;
    import org.elasticsearch.search.SearchHit;
    import org.elasticsearch.search.SearchHits;
    import org.elasticsearch.search.builder.SearchSourceBuilder;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    import java.io.IOException;
    /**
     * 模糊查询
     */
    @Slf4j
    @Service
    public class FuzzyQueryService {
        @Autowired
        private RestHighLevelClient restHighLevelClient;
        /**
         * 模糊查询所有以 “三” 结尾的姓名
         * @param queryDto
         */
        public Object fuzzyQuery(MatchQueryDto queryDto) {
            Object result = "";
            try {
                // 构建查询条件
                SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
                searchSourceBuilder.query(QueryBuilders.fuzzyQuery(queryDto.getKey(), queryDto.getValue()).fuzziness(Fuzziness.AUTO));
                // 创建查询请求对象,将查询对象配置到其中
                SearchRequest searchRequest = new SearchRequest(queryDto.getIndexName());
                searchRequest.source(searchSourceBuilder);
                // 执行查询,然后处理响应结果
                SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
                // 根据状态和数据条数验证是否返回了数据
                if (RestStatus.OK.equals(searchResponse.status()) && searchResponse.getHits().getTotalHits().value > 0) {
                    SearchHits hits = searchResponse.getHits();
                    for (SearchHit hit : hits) {
                        // 将 JSON 转换成对象
                        UserInfo userInfo = JSON.parseObject(hit.getSourceAsString(), UserInfo.class);
                        // 输出查询信息
                        log.info(userInfo.toString());
                    }
                }
                result = searchResponse.getHits();
            } catch (IOException e) {
                log.error("", e);
            }
            return result;
        }
    }
    

    4、范围查询(range)

    (1)、Restful 操作示例

    查询岁数 ≥ 30 岁的员工数据:
    GET http://localhost:9200/testindex/_search
    //请求
    {
      "query": {
        "range": {
          "age": {
            "gte": 30
          }
        }
      }
    }
    查询生日距离现在 30 年间的员工数据: 
    GET http://localhost:9200/testindex/_search
    //请求
    {
      "query": {
        "range": {
          "birthDate": {
            "gte": "now-30y"
          }
        }
      }
    }

    (2)、Java 代码示例 

    package com.example.elasticsearch.demos.web.service.query;
    import com.alibaba.fastjson.JSON;
    import com.example.elasticsearch.demos.web.model.dto.MatchQueryDto;
    import com.example.elasticsearch.demos.web.model.entity.UserInfo;
    import lombok.extern.slf4j.Slf4j;
    import org.elasticsearch.action.search.SearchRequest;
    import org.elasticsearch.action.search.SearchResponse;
    import org.elasticsearch.client.RequestOptions;
    import org.elasticsearch.client.RestHighLevelClient;
    import org.elasticsearch.index.query.QueryBuilders;
    import org.elasticsearch.rest.RestStatus;
    import org.elasticsearch.search.SearchHit;
    import org.elasticsearch.search.SearchHits;
    import org.elasticsearch.search.builder.SearchSourceBuilder;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    import java.io.IOException;
    /**
     * 范围查询
     */
    @Slf4j
    @Service
    public class RangeQueryService {
        @Autowired
        private RestHighLevelClient restHighLevelClient;
        /**
         * 查询岁数 ≥ 30 岁的员工数据
         * @param queryDto
         */
        public Object rangeQuery(MatchQueryDto queryDto) {
            Object result = "";
            try {
                // 构建查询条件
                SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
                searchSourceBuilder.query(QueryBuilders.rangeQuery("age").gte(30));
                // 创建查询请求对象,将查询对象配置到其中
                SearchRequest searchRequest = new SearchRequest(queryDto.getIndexName());
                searchRequest.source(searchSourceBuilder);
                // 执行查询,然后处理响应结果
                SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
                // 根据状态和数据条数验证是否返回了数据
                if (RestStatus.OK.equals(searchResponse.status()) && searchResponse.getHits().getTotalHits().value > 0) {
                    SearchHits hits = searchResponse.getHits();
                    for (SearchHit hit : hits) {
                        // 将 JSON 转换成对象
                        UserInfo userInfo = JSON.parseObject(hit.getSourceAsString(), UserInfo.class);
                        // 输出查询信息
                        log.info(userInfo.toString());
                    }
                }
                result = searchResponse.getHits();
            } catch (IOException e) {
                log.error("", e);
            }
            return result;
        }
        /**
         * 查询距离现在 30 年间的员工数据
         * [年(y)、月(M)、星期(w)、天(d)、小时(h)、分钟(m)、秒(s)]
         * 例如:
         * now-1h 查询一小时内范围
         * now-1d 查询一天内时间范围
         * now-1y 查询最近一年内的时间范围
         * @param queryDto
         */
        public Object dateRangeQuery(MatchQueryDto queryDto) {
            Object result = "";
            try {
                // 构建查询条件
                SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
    //            // includeLower(是否包含下边界)、includeUpper(是否包含上边界)
    //            searchSourceBuilder.query(QueryBuilders.rangeQuery("birthDate")
    //                    .gte("now-30y").includeLower(true).includeUpper(true));
                searchSourceBuilder.query(QueryBuilders.rangeQuery("birthDate").gte(queryDto.getFrom()).lte(queryDto.getEnd()));
                // 创建查询请求对象,将查询对象配置到其中
                SearchRequest searchRequest = new SearchRequest(queryDto.getIndexName());
                searchRequest.source(searchSourceBuilder);
                // 执行查询,然后处理响应结果
                SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
                // 根据状态和数据条数验证是否返回了数据
                if (RestStatus.OK.equals(searchResponse.status()) && searchResponse.getHits().getTotalHits().value > 0) {
                    SearchHits hits = searchResponse.getHits();
                    for (SearchHit hit : hits) {
                        // 将 JSON 转换成对象
                        UserInfo userInfo = JSON.parseObject(hit.getSourceAsString(), UserInfo.class);
                        // 输出查询信息
                        log.info(userInfo.toString());
                    }
                }
                result = searchResponse.getHits();
            } catch (IOException e) {
                log.error("", e);
            }
            return result;
        }
    }
    

    5、通配符查询(wildcard)

    (1)、Restful 操作示例

    查询所有以 “三” 结尾的姓名:
    GET http://localhost:9200/testindex/_search
    //请求
    {
      "query": {
        "wildcard": {
          "name.keyword": {
            "value": "*三"
          }
        }
      }
    }

    (2)、Java 代码示例 

    package com.example.elasticsearch.demos.web.service.query;
    import com.alibaba.fastjson.JSON;
    import com.example.elasticsearch.demos.web.model.dto.MatchQueryDto;
    import com.example.elasticsearch.demos.web.model.entity.UserInfo;
    import lombok.extern.slf4j.Slf4j;
    import org.elasticsearch.action.search.SearchRequest;
    import org.elasticsearch.action.search.SearchResponse;
    import org.elasticsearch.client.RequestOptions;
    import org.elasticsearch.client.RestHighLevelClient;
    import org.elasticsearch.index.query.QueryBuilders;
    import org.elasticsearch.rest.RestStatus;
    import org.elasticsearch.search.SearchHit;
    import org.elasticsearch.search.SearchHits;
    import org.elasticsearch.search.builder.SearchSourceBuilder;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    import java.io.IOException;
    /**
     * 通配符查询
     */
    @Slf4j
    @Service
    public class WildcardQueryService {
        @Autowired
        private RestHighLevelClient restHighLevelClient;
        /**
         * 查询所有以 “三” 结尾的姓名
         * 

    * *:表示多个字符(0个或多个字符) * ?:表示单个字符 * @param queryDto */ public Object wildcardQuery(MatchQueryDto queryDto) { Object result = ""; try { // 构建查询条件 SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); searchSourceBuilder.query(QueryBuilders.wildcardQuery(queryDto.getKey() + ".keyword", "*" + queryDto.getValue())); // 创建查询请求对象,将查询对象配置到其中 SearchRequest searchRequest = new SearchRequest(queryDto.getIndexName()); searchRequest.source(searchSourceBuilder); // 执行查询,然后处理响应结果 SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT); // 根据状态和数据条数验证是否返回了数据 if (RestStatus.OK.equals(searchResponse.status()) && searchResponse.getHits().getTotalHits().value > 0) { SearchHits hits = searchResponse.getHits(); for (SearchHit hit : hits) { // 将 JSON 转换成对象 UserInfo userInfo = JSON.parseObject(hit.getSourceAsString(), UserInfo.class); // 输出查询信息 log.info(userInfo.toString()); } } result = searchResponse.getHits(); } catch (IOException e) { log.error("", e); } return result; } }

    6、布尔查询(bool)

    (1)、Restful 操作示例

    查询出生在 1990-1995 年期间,且地址在 北京市昌平区、北京市大兴区、北京市房山区 的员工信息:

    GET http://localhost:9200/testindex/_search
    //请求
    {
      "query": {
        "bool": {
          "filter": {
            "range": {
              "birthDate": {
                "format": "yyyy", 
                "gte": 1990,
                "lte": 1995
              }
            }
          },
          "must": [
            {
              "terms": {
                "address.keyword": [
                  "北京市昌平区",
                  "北京市大兴区",
                  "北京市房山区"
                ]
              }
            }
          ]
        }
      }
    }

     (2)、Java 代码示例

    package com.example.elasticsearch.demos.web.service.query;
    import com.alibaba.fastjson.JSON;
    import com.example.elasticsearch.demos.web.model.dto.MatchQueryDto;
    import com.example.elasticsearch.demos.web.model.entity.UserInfo;
    import lombok.extern.slf4j.Slf4j;
    import org.elasticsearch.action.search.SearchRequest;
    import org.elasticsearch.action.search.SearchResponse;
    import org.elasticsearch.client.RequestOptions;
    import org.elasticsearch.client.RestHighLevelClient;
    import org.elasticsearch.index.query.BoolQueryBuilder;
    import org.elasticsearch.index.query.QueryBuilders;
    import org.elasticsearch.rest.RestStatus;
    import org.elasticsearch.search.SearchHit;
    import org.elasticsearch.search.SearchHits;
    import org.elasticsearch.search.builder.SearchSourceBuilder;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    import java.io.IOException;
    /**
     * 布尔查询
     */
    @Slf4j
    @Service
    public class BoolQueryService {
        @Autowired
        private RestHighLevelClient restHighLevelClient;
        /**
         * 布尔查询
         * @param queryDto
         */
        public Object boolQuery(MatchQueryDto queryDto) {
            Object result = "";
            try {
                // 创建 Bool 查询构建器
                BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
                // 构建查询条件
                boolQueryBuilder.must(QueryBuilders.termsQuery("address.keyword", "北京市昌平区", "北京市大兴区", "北京市房山区"))
                        .filter().add(QueryBuilders.rangeQuery("birthDate").format("yyyy").gte("1990").lte("1995"));
                // 构建查询源构建器
                SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
                searchSourceBuilder.query(boolQueryBuilder);
                // 创建查询请求对象,将查询对象配置到其中
                SearchRequest searchRequest = new SearchRequest(queryDto.getIndexName());
                searchRequest.source(searchSourceBuilder);
                // 执行查询,然后处理响应结果
                SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
                // 根据状态和数据条数验证是否返回了数据
                if (RestStatus.OK.equals(searchResponse.status()) && searchResponse.getHits().getTotalHits().value > 0) {
                    SearchHits hits = searchResponse.getHits();
                    for (SearchHit hit : hits) {
                        // 将 JSON 转换成对象
                        UserInfo userInfo = JSON.parseObject(hit.getSourceAsString(), UserInfo.class);
                        // 输出查询信息
                        log.info(userInfo.toString());
                    }
                }
                result = searchResponse.getHits();
            } catch (IOException e) {
                log.error("", e);
            }
            return result;
        }
    }
    

    八、聚合查询操作示例

    1、Metric 聚合分析

    (1)、Restful 操作示例

    GET http://localhost:9200/testindex/_search
    1、统计员工总数、工资最高值、工资最低值、工资平均工资、工资总和:
    //请求
    {
      "size": 0,
      "aggs": {
        "salary_stats": {
          "stats": {
            "field": "salary"
          }
        }
      }
    }
    2、统计员工工资最低值:
    //请求
    {
      "size": 0,
      "aggs": {
        "salary_min": {
          "min": {
            "field": "salary"
          }
        }
      }
    }
    3、统计员工工资最高值:
    //请求
    {
      "size": 0,
      "aggs": {
        "salary_max": {
          "max": {
            "field": "salary"
          }
        }
      }
    }
    4、统计员工工资平均值:
    //请求
    {
      "size": 0,
      "aggs": {
        "salary_avg": {
          "avg": {
            "field": "salary"
          }
        }
      }
    }
    5、统计员工工资总值:
    //请求
    {
      "size": 0,
      "aggs": {
        "salary_sum": {
          "sum": {
            "field": "salary"
          }
        }
      }
    }
    6、统计员工总数:
    //请求
    {
      "size": 0,
      "aggs": {
        "employee_count": {
          "value_count": {
            "field": "salary"
          }
        }
      }
    }
    7、统计员工工资百分位:
    //请求
    {
      "size": 0,
      "aggs": {
        "salary_percentiles": {
          "percentiles": {
            "field": "salary"
          }
        }
      }
    }

    (2)、Java 代码示例

    package com.example.elasticsearch.demos.web.service.aggregation;
    import com.example.elasticsearch.demos.web.model.dto.MatchQueryDto;
    import lombok.extern.slf4j.Slf4j;
    import org.elasticsearch.action.search.SearchRequest;
    import org.elasticsearch.action.search.SearchResponse;
    import org.elasticsearch.client.RequestOptions;
    import org.elasticsearch.client.RestHighLevelClient;
    import org.elasticsearch.rest.RestStatus;
    import org.elasticsearch.search.aggregations.AggregationBuilder;
    import org.elasticsearch.search.aggregations.AggregationBuilders;
    import org.elasticsearch.search.aggregations.Aggregations;
    import org.elasticsearch.search.aggregations.metrics.*;
    import org.elasticsearch.search.builder.SearchSourceBuilder;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.stereotype.Component;
    import org.springframework.stereotype.Service;
    import java.io.IOException;
    /**
     * 聚合 Metric
     */
    @Slf4j
    @Service
    public class AggrMetricService {
        @Autowired
        private RestHighLevelClient restHighLevelClient;
        @Value("${myindex}")
        private String indexName;
        /**
         * stats 统计员工总数、工资最高值、工资最低值、工资平均工资、工资总和
         * @param queryDto
         */
        public Object aggregationStats(MatchQueryDto queryDto) {
            String responseResult = "";
            try {
                // 设置聚合条件
                String field = queryDto.getKey();
                AggregationBuilder aggr = AggregationBuilders.stats(field + "_stats").field(field);
                // 查询源构建器
                SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
                searchSourceBuilder.aggregation(aggr);
                // 设置查询结果不返回,只返回聚合结果
                searchSourceBuilder.size(0);
                // 创建查询请求对象,将查询条件配置到其中
                SearchRequest request = new SearchRequest(indexName);
                request.source(searchSourceBuilder);
                // 执行请求
                SearchResponse response = restHighLevelClient.search(request, RequestOptions.DEFAULT);
                // 获取响应中的聚合信息
                Aggregations aggregations = response.getAggregations();
                // 输出内容
                if (RestStatus.OK.equals(response.status()) || aggregations != null) {
                    // 转换为 Stats 对象
                    ParsedStats aggregation = aggregations.get(field + "_stats");
                    log.info("-------------------------------------------");
                    log.info("聚合信息: {}", field);
                    log.info("count:{}", aggregation.getCount());
                    log.info("avg:{}", aggregation.getAvg());
                    log.info("max:{}", aggregation.getMax());
                    log.info("min:{}", aggregation.getMin());
                    log.info("sum:{}", aggregation.getSum());
                    log.info("-------------------------------------------");
                }
                // 根据具体业务逻辑返回不同结果,这里为了方便直接将返回响应对象Json串
                responseResult = response.toString();
            } catch (IOException e) {
                log.error("", e);
            }
            return responseResult;
        }
        /**
         * min 统计员工工资最低值
         */
        public Object aggregationMin() {
            String responseResult = "";
            try {
                // 设置聚合条件
                AggregationBuilder aggr = AggregationBuilders.min("salary_min").field("salary");
                // 查询源构建器
                SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
                searchSourceBuilder.aggregation(aggr);
                searchSourceBuilder.size(0);
                // 创建查询请求对象,将查询条件配置到其中
                SearchRequest request = new SearchRequest(indexName);
                request.source(searchSourceBuilder);
                // 执行请求
                SearchResponse response = restHighLevelClient.search(request, RequestOptions.DEFAULT);
                // 获取响应中的聚合信息
                Aggregations aggregations = response.getAggregations();
                // 输出内容
                if (RestStatus.OK.equals(response.status()) || aggregations != null) {
                    // 转换为 Min 对象
                    ParsedMin aggregation = aggregations.get("salary_min");
                    log.info("-------------------------------------------");
                    log.info("聚合信息:");
                    log.info("min:{}", aggregation.getValue());
                    log.info("-------------------------------------------");
                }
                // 根据具体业务逻辑返回不同结果,这里为了方便直接将返回响应对象Json串
                responseResult = response.toString();
            } catch (IOException e) {
                log.error("", e);
            }
            return responseResult;
        }
        /**
         * max 统计员工工资最高值
         */
        public Object aggregationMax() {
            String responseResult = "";
            try {
                // 设置聚合条件
                AggregationBuilder aggr = AggregationBuilders.max("salary_max").field("salary");
                // 查询源构建器
                SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
                searchSourceBuilder.aggregation(aggr);
                searchSourceBuilder.size(0);
                // 创建查询请求对象,将查询条件配置到其中
                SearchRequest request = new SearchRequest(indexName);
                request.source(searchSourceBuilder);
                // 执行请求
                SearchResponse response = restHighLevelClient.search(request, RequestOptions.DEFAULT);
                // 获取响应中的聚合信息
                Aggregations aggregations = response.getAggregations();
                // 输出内容
                if (RestStatus.OK.equals(response.status()) || aggregations != null) {
                    // 转换为 Max 对象
                    ParsedMax aggregation = aggregations.get("salary_max");
                    log.info("-------------------------------------------");
                    log.info("聚合信息:");
                    log.info("max:{}", aggregation.getValue());
                    log.info("-------------------------------------------");
                }
                // 根据具体业务逻辑返回不同结果,这里为了方便直接将返回响应对象Json串
                responseResult = response.toString();
            } catch (IOException e) {
                log.error("", e);
            }
            return responseResult;
        }
        /**
         * avg 统计员工工资平均值
         */
        public Object aggregationAvg() {
            String responseResult = "";
            try {
                // 设置聚合条件
                AggregationBuilder aggr = AggregationBuilders.avg("salary_avg").field("salary");
                // 查询源构建器
                SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
                searchSourceBuilder.aggregation(aggr);
                searchSourceBuilder.size(0);
                // 创建查询请求对象,将查询条件配置到其中
                SearchRequest request = new SearchRequest(indexName);
                request.source(searchSourceBuilder);
                // 执行请求
                SearchResponse response = restHighLevelClient.search(request, RequestOptions.DEFAULT);
                // 获取响应中的聚合信息
                Aggregations aggregations = response.getAggregations();
                // 输出内容
                if (RestStatus.OK.equals(response.status()) || aggregations != null) {
                    // 转换为 Avg 对象
                    ParsedAvg aggregation = aggregations.get("salary_avg");
                    log.info("-------------------------------------------");
                    log.info("聚合信息:");
                    log.info("avg:{}", aggregation.getValue());
                    log.info("-------------------------------------------");
                }
                // 根据具体业务逻辑返回不同结果,这里为了方便直接将返回响应对象Json串
                responseResult = response.toString();
            } catch (IOException e) {
                log.error("", e);
            }
            return responseResult;
        }
        /**
         * sum 统计员工工资总值
         */
        public Object aggregationSum() {
            String responseResult = "";
            try {
                // 设置聚合条件
                SumAggregationBuilder aggr = AggregationBuilders.sum("salary_sum").field("salary");
                // 查询源构建器
                SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
                searchSourceBuilder.aggregation(aggr);
                searchSourceBuilder.size(0);
                // 创建查询请求对象,将查询条件配置到其中
                SearchRequest request = new SearchRequest(indexName);
                request.source(searchSourceBuilder);
                // 执行请求
                SearchResponse response = restHighLevelClient.search(request, RequestOptions.DEFAULT);
                // 获取响应中的聚合信息
                Aggregations aggregations = response.getAggregations();
                // 输出内容
                if (RestStatus.OK.equals(response.status()) || aggregations != null) {
                    // 转换为 Sum 对象
                    ParsedSum aggregation = aggregations.get("salary_sum");
                    log.info("-------------------------------------------");
                    log.info("聚合信息:");
                    log.info("sum:{}", String.valueOf((aggregation.getValue())));
                    log.info("-------------------------------------------");
                }
                // 根据具体业务逻辑返回不同结果,这里为了方便直接将返回响应对象Json串
                responseResult = response.toString();
            } catch (IOException e) {
                log.error("", e);
            }
            return responseResult;
        }
        /**
         * count 统计员工总数
         */
        public Object aggregationCount() {
            String responseResult = "";
            try {
                // 设置聚合条件
                AggregationBuilder aggr = AggregationBuilders.count("employee_count").field("salary");
                // 查询源构建器
                SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
                searchSourceBuilder.aggregation(aggr);
                searchSourceBuilder.size(0);
                // 创建查询请求对象,将查询条件配置到其中
                SearchRequest request = new SearchRequest(indexName);
                request.source(searchSourceBuilder);
                // 执行请求
                SearchResponse response = restHighLevelClient.search(request, RequestOptions.DEFAULT);
                // 获取响应中的聚合信息
                Aggregations aggregations = response.getAggregations();
                // 输出内容
                if (RestStatus.OK.equals(response.status()) || aggregations != null) {
                    // 转换为 ValueCount 对象
                    ParsedValueCount aggregation = aggregations.get("employee_count");
                    log.info("-------------------------------------------");
                    log.info("聚合信息:");
                    log.info("count:{}", aggregation.getValue());
                    log.info("-------------------------------------------");
                }
                // 根据具体业务逻辑返回不同结果,这里为了方便直接将返回响应对象Json串
                responseResult = response.toString();
            } catch (IOException e) {
                log.error("", e);
            }
            return responseResult;
        }
        /**
         * percentiles 统计员工工资百分位
         */
        public Object aggregationPercentiles() {
            String responseResult = "";
            try {
                // 设置聚合条件
                AggregationBuilder aggr = AggregationBuilders.percentiles("salary_percentiles").field("salary");
                // 查询源构建器
                SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
                searchSourceBuilder.aggregation(aggr);
                searchSourceBuilder.size(0);
                // 创建查询请求对象,将查询条件配置到其中
                SearchRequest request = new SearchRequest(indexName);
                request.source(searchSourceBuilder);
                // 执行请求
                SearchResponse response = restHighLevelClient.search(request, RequestOptions.DEFAULT);
                // 获取响应中的聚合信息
                Aggregations aggregations = response.getAggregations();
                // 输出内容
                if (RestStatus.OK.equals(response.status()) || aggregations != null) {
                    // 转换为 Percentiles 对象
                    ParsedPercentiles aggregation = aggregations.get("salary_percentiles");
                    log.info("-------------------------------------------");
                    log.info("聚合信息:");
                    for (Percentile percentile : aggregation) {
                        log.info("百分位:{}:{}", percentile.getPercent(), percentile.getValue());
                    }
                    log.info("-------------------------------------------");
                }
                // 根据具体业务逻辑返回不同结果,这里为了方便直接将返回响应对象Json串
                responseResult = response.toString();
            } catch (IOException e) {
                log.error("", e);
            }
            return responseResult;
        }
    }
    

    2、Bucket 聚合分析

    (1)、Restful 操作示例

    GET http://localhost:9200/testindex/_search
    1、按岁数进行聚合分桶,统计各个岁数员工的人数:
    //请求
    {
      "size": 0,
      "aggs": {
        "age_bucket": {
          "terms": {
            "field": "age",
            "size": "10"
          }
        }
      }
    }
    2、按工资范围进行聚合分桶,统计工资在 3000-5000、5000-9000 和 9000 以上的员工信息:
    //请求
    {
      "aggs": {
        "salary_range_bucket": {
          "range": {
            "field": "salary",
            "ranges": [
              {
                "key": "低级员工", 
                "to": 3000
              },{
                "key": "中级员工",
                "from": 5000,
                "to": 9000
              },{
                "key": "高级员工",
                "from": 9000
              }
            ]
          }
        }
      }
    }
    3、按照时间范围进行分桶,统计 1985-1990 年和 1990-1995 年出生的员工信息:
    //请求
    {
      "size": 10,
      "aggs": {
        "date_range_bucket": {
          "date_range": {
            "field": "birthDate",
            "format": "yyyy", 
            "ranges": [
              {
                "key": "出生日期1985-1990的员工", 
                "from": "1985",
                "to": "1990"
              },{
                "key": "出生日期1990-1995的员工", 
                "from": "1990",
                "to": "1995"
              }
            ]
          }
        }
      }
    }
    4、按工资多少进行聚合分桶,设置统计的最小值为 0,最大值为 12000,区段间隔为 3000:
    //请求
    {
      "size": 0,
      "aggs": {
        "salary_histogram": {
          "histogram": {
            "field": "salary",
            "extended_bounds": {
              "min": 0,
              "max": 12000
            }, 
            "interval": 3000
          }
        }
      }
    }
    5、按出生日期进行分桶:
    //请求
    {
      "size": 0,
      "aggs": {
        "birthday_histogram": {
          "date_histogram": {
            "format": "yyyy", 
            "field": "birthDate",
            "interval": "year"
          }
        }
      }
    }

    (2)、Java 代码示例 

    package com.example.elasticsearch.demos.web.service.aggregation;
    import lombok.extern.slf4j.Slf4j;
    import org.elasticsearch.action.search.SearchRequest;
    import org.elasticsearch.action.search.SearchResponse;
    import org.elasticsearch.client.RequestOptions;
    import org.elasticsearch.client.RestHighLevelClient;
    import org.elasticsearch.rest.RestStatus;
    import org.elasticsearch.search.aggregations.AggregationBuilder;
    import org.elasticsearch.search.aggregations.AggregationBuilders;
    import org.elasticsearch.search.aggregations.Aggregations;
    import org.elasticsearch.search.aggregations.bucket.histogram.DateHistogramInterval;
    import org.elasticsearch.search.aggregations.bucket.histogram.Histogram;
    import org.elasticsearch.search.aggregations.bucket.range.Range;
    import org.elasticsearch.search.aggregations.bucket.terms.Terms;
    import org.elasticsearch.search.builder.SearchSourceBuilder;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.stereotype.Service;
    import java.io.IOException;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    /**
     * 聚合 Bucket
     */
    @Slf4j
    @Service
    public class AggrBucketService {
        @Autowired
        private RestHighLevelClient restHighLevelClient;
        @Value("${myindex}")
        private String indexName;
        /**
         * 按岁数进行聚合分桶,统计各个岁数员工的人数:
         */
        public Object aggrBucketTerms() {
            Map keyCountMap = new HashMap<>();
            try {
                AggregationBuilder aggr = AggregationBuilders.terms("age_bucket").field("age");
                // 查询源构建器
                SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
                searchSourceBuilder.size(10);
                searchSourceBuilder.aggregation(aggr);
                // 创建查询请求对象,将查询条件配置到其中
                SearchRequest request = new SearchRequest(indexName);
                request.source(searchSourceBuilder);
                // 执行请求
                SearchResponse response = restHighLevelClient.search(request, RequestOptions.DEFAULT);
                // 获取响应中的聚合信息
                Aggregations aggregations = response.getAggregations();
                // 输出内容
                if (RestStatus.OK.equals(response.status())) {
                    // 分桶
                    Terms byCompanyAggregation = aggregations.get("age_bucket");
                    List buckets = byCompanyAggregation.getBuckets();
                    // 输出各个桶的内容
                    log.info("-------------------------------------------");
                    log.info("聚合信息:");
                    for (Terms.Bucket bucket : buckets) {
                        keyCountMap.put(bucket.getKeyAsString(),  bucket.getDocCount());
                        log.info("桶名:{} | 总数:{}", bucket.getKeyAsString(), bucket.getDocCount());
                    }
                    log.info("-------------------------------------------");
                }
            } catch (IOException e) {
                log.error("", e);
            }
            return keyCountMap;
        }
        /**
         * 按工资范围进行聚合分桶,统计工资在 3000-5000、5000-9000 和 9000 以上的员工信息:
         */
        public Object aggrBucketRange() {
            Map keyCountMap = new HashMap<>();
            try {
                AggregationBuilder aggr = AggregationBuilders.range("salary_range_bucket")
                        .field("salary")
                        .addUnboundedTo("低级员工", 3000)
                        .addRange("中级员工", 5000, 9000)
                        .addUnboundedFrom("高级员工", 9000);
                // 查询源构建器
                SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
                searchSourceBuilder.size(0);
                searchSourceBuilder.aggregation(aggr);
                // 创建查询请求对象,将查询条件配置到其中
                SearchRequest request = new SearchRequest(indexName);
                request.source(searchSourceBuilder);
                // 执行请求
                SearchResponse response = restHighLevelClient.search(request, RequestOptions.DEFAULT);
                // 获取响应中的聚合信息
                Aggregations aggregations = response.getAggregations();
                // 输出内容
                if (RestStatus.OK.equals(response.status())) {
                    // 分桶
                    Range byCompanyAggregation = aggregations.get("salary_range_bucket");
                    List buckets = byCompanyAggregation.getBuckets();
                    // 输出各个桶的内容
                    log.info("-------------------------------------------");
                    log.info("聚合信息:");
                    for (Range.Bucket bucket : buckets) {
                        keyCountMap.put(bucket.getKeyAsString(),  bucket.getDocCount());
                        log.info("桶名:{} | 总数:{}", bucket.getKeyAsString(), bucket.getDocCount());
                    }
                    log.info("-------------------------------------------");
                }
            } catch (IOException e) {
                log.error("", e);
            }
            return keyCountMap;
        }
        /**
         * 按照时间范围进行分桶,统计 1985-1990 年和 1990-1995 年出生的员工信息:
         */
        public Object aggrBucketDateRange() {
            Map keyCountMap = new HashMap<>();
            try {
                AggregationBuilder aggr = AggregationBuilders.dateRange("date_range_bucket")
                        .field("birthDate")
                        .format("yyyy")
                        .addRange("出生日期1985-1990的员工", "1985", "1990")
                        .addRange("出生日期1990-1995的员工", "1990", "1995");
                // 查询源构建器
                SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
                searchSourceBuilder.size(0);
                searchSourceBuilder.aggregation(aggr);
                // 创建查询请求对象,将查询条件配置到其中
                SearchRequest request = new SearchRequest(indexName);
                request.source(searchSourceBuilder);
                // 执行请求
                SearchResponse response = restHighLevelClient.search(request, RequestOptions.DEFAULT);
                // 获取响应中的聚合信息
                Aggregations aggregations = response.getAggregations();
                // 输出内容
                if (RestStatus.OK.equals(response.status())) {
                    // 分桶
                    Range byCompanyAggregation = aggregations.get("date_range_bucket");
                    List buckets = byCompanyAggregation.getBuckets();
                    // 输出各个桶的内容
                    log.info("-------------------------------------------");
                    log.info("聚合信息:");
                    for (Range.Bucket bucket : buckets) {
                        keyCountMap.put(bucket.getKeyAsString(),  bucket.getDocCount());
                        log.info("桶名:{} | 总数:{}", bucket.getKeyAsString(), bucket.getDocCount());
                    }
                    log.info("-------------------------------------------");
                }
            } catch (IOException e) {
                log.error("", e);
            }
            return keyCountMap;
        }
        /**
         * 按工资多少进行聚合分桶
         */
        public Object aggrBucketHistogram() {
            Map keyCountMap = new HashMap<>();
            try {
                //按工资多少进行聚合分桶,设置统计的最小值为 0,最大值为 12000,区段间隔为 3000:
                AggregationBuilder aggr = AggregationBuilders.histogram("salary_histogram")
                        .field("salary")
                        .extendedBounds(0, 12000)
                        .interval(3000);
                // 查询源构建器
                SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
                searchSourceBuilder.size(0);
                searchSourceBuilder.aggregation(aggr);
                // 创建查询请求对象,将查询条件配置到其中
                SearchRequest request = new SearchRequest(indexName);
                request.source(searchSourceBuilder);
                // 执行请求
                SearchResponse response = restHighLevelClient.search(request, RequestOptions.DEFAULT);
                // 获取响应中的聚合信息
                Aggregations aggregations = response.getAggregations();
                // 输出内容
                if (RestStatus.OK.equals(response.status())) {
                    // 分桶
                    Histogram byCompanyAggregation = aggregations.get("salary_histogram");
                    List buckets = byCompanyAggregation.getBuckets();
                    // 输出各个桶的内容
                    log.info("-------------------------------------------");
                    log.info("聚合信息:");
                    for (Histogram.Bucket bucket : buckets) {
                        keyCountMap.put(bucket.getKeyAsString(),  bucket.getDocCount());
                        log.info("桶名:{} | 总数:{}", bucket.getKeyAsString(), bucket.getDocCount());
                    }
                    log.info("-------------------------------------------");
                }
            } catch (IOException e) {
                log.error("", e);
            }
            return keyCountMap;
        }
        /**
         * 按出生日期进行分桶:
         */
        public Object aggrBucketDateHistogram() {
            Map keyCountMap = new HashMap<>();
            try {
                AggregationBuilder aggr = AggregationBuilders.dateHistogram("birthday_histogram")
                        .field("birthDate")
                        .interval(1)
                        .dateHistogramInterval(DateHistogramInterval.YEAR)
                        .format("yyyy");
                // 查询源构建器
                SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
                searchSourceBuilder.size(0);
                searchSourceBuilder.aggregation(aggr);
                // 创建查询请求对象,将查询条件配置到其中
                SearchRequest request = new SearchRequest(indexName);
                request.source(searchSourceBuilder);
                // 执行请求
                SearchResponse response = restHighLevelClient.search(request, RequestOptions.DEFAULT);
                // 获取响应中的聚合信息
                Aggregations aggregations = response.getAggregations();
                // 输出内容
                if (RestStatus.OK.equals(response.status())) {
                    // 分桶
                    Histogram byCompanyAggregation = aggregations.get("birthday_histogram");
                    List buckets = byCompanyAggregation.getBuckets();
                    // 输出各个桶的内容
                    log.info("-------------------------------------------");
                    log.info("聚合信息:");
                    for (Histogram.Bucket bucket : buckets) {
                        keyCountMap.put(bucket.getKeyAsString(),  bucket.getDocCount());
                        log.info("桶名:{} | 总数:{}", bucket.getKeyAsString(), bucket.getDocCount());
                    }
                    log.info("-------------------------------------------");
                }
            } catch (IOException e) {
                log.error("", e);
            }
            return keyCountMap;
        }
    }
    

    3、Metric 与 Bucket 聚合分析

    (1)、Restful 操作示例

    按照员工岁数分桶、然后统计每个岁数员工工资最高值:

    GET http://localhost:9200/testindex/_search
    //请求
    {
      "size": 0,
      "aggs": {
        "salary_bucket": {
          "terms": {
            "field": "age",
            "size": "10"
          },
          "aggs": {
            "salary_max_user": {
              "top_hits": {
                "size": 1,
                "sort": [
                  {
                    "salary": {
                      "order": "desc"
                    }
                  }
                ]
              }
            }
          }
        }
      }
    }

    (2)、Java 代码示例

    package com.example.elasticsearch.demos.web.service.aggregation;
    import com.alibaba.fastjson.JSON;
    import com.example.elasticsearch.demos.web.model.entity.UserInfo;
    import lombok.extern.slf4j.Slf4j;
    import org.elasticsearch.action.search.SearchRequest;
    import org.elasticsearch.action.search.SearchResponse;
    import org.elasticsearch.client.RequestOptions;
    import org.elasticsearch.client.RestHighLevelClient;
    import org.elasticsearch.rest.RestStatus;
    import org.elasticsearch.search.SearchHit;
    import org.elasticsearch.search.aggregations.AggregationBuilder;
    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.metrics.ParsedTopHits;
    import org.elasticsearch.search.builder.SearchSourceBuilder;
    import org.elasticsearch.search.sort.SortOrder;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.stereotype.Service;
    import java.io.IOException;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    /**
     * 聚合 Bucket 与 Metric
     */
    @Slf4j
    @Service
    public class AggrBucketMetricService {
        @Autowired
        private RestHighLevelClient restHighLevelClient;
        @Value("${myindex}")
        private String indexName;
        /**
         * topHits 按照员工岁数分桶、然后统计每个岁数员工工资最高值
         */
        public Object aggregationTopHits() {
            Map ageMaxSalaryMap = new HashMap<>();
            try {
                AggregationBuilder testTop = AggregationBuilders.topHits("salary_max_user")
                        .size(1)
                        .sort("salary", SortOrder.DESC);
                AggregationBuilder salaryBucket = AggregationBuilders.terms("salary_bucket")
                        .field("age")
                        .size(10);
                salaryBucket.subAggregation(testTop);
                // 查询源构建器
                SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
                searchSourceBuilder.size(0);
                searchSourceBuilder.aggregation(salaryBucket);
                // 创建查询请求对象,将查询条件配置到其中
                SearchRequest request = new SearchRequest(indexName);
                request.source(searchSourceBuilder);
                // 执行请求
                SearchResponse response = restHighLevelClient.search(request, RequestOptions.DEFAULT);
                // 获取响应中的聚合信息
                Aggregations aggregations = response.getAggregations();
                // 输出内容
                if (RestStatus.OK.equals(response.status())) {
                    // 分桶
                    Terms byCompanyAggregation = aggregations.get("salary_bucket");
                    List buckets = byCompanyAggregation.getBuckets();
                    // 输出各个桶的内容
                    log.info("-------------------------------------------");
                    log.info("聚合信息:");
                    for (Terms.Bucket bucket : buckets) {
                        log.info("桶名:{}", bucket.getKeyAsString());
                        ParsedTopHits topHits = bucket.getAggregations().get("salary_max_user");
                        for (SearchHit hit : topHits.getHits()) {
                            UserInfo userInfo = JSON.parseObject(hit.getSourceAsString(), UserInfo.class);
                            ageMaxSalaryMap.put(bucket.getKeyAsString(),  userInfo.getSalary());
                            log.info(hit.getSourceAsString());
                        }
                    }
                    log.info("-------------------------------------------");
                }
            } catch (IOException e) {
                log.error("", e);
            }
            return ageMaxSalaryMap;
        }
    }
    

    九、项目源码及对应ES安装包

    1、elasticsearch-7.6.1安装包

    elasticsearch7.6.1基于SpringBoot的ElasticSearch操作(超详细教程),icon-default.png?t=N7T8,第3张https://download.csdn.net/download/asd051377305/88397087

    2、项目源代码

    基于SpringBoot+elasticsearch的操作项目基于SpringBoot的ElasticSearch操作(超详细教程),icon-default.png?t=N7T8,第3张https://download.csdn.net/download/asd051377305/88397090