基于SpringBoot的ElasticSearch操作(超详细教程)

一、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的版本对应】

1、Maven 引入相关依赖

  • lombok:lombok 工具依赖。
  • fastjson:用于将 JSON 转换对象的依赖。
  • spring-boot-starter-web: SpringBoot 的 Web 依赖。
  • elasticsearch:ElasticSearch:依赖,需要和 ES 版本保持一致。
  • elasticsearch-rest-high-level-client:用于操作 ES 的 Java 客户端。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.example</groupId>
    <artifactId>elasticsearch</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>elasticsearch</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>1.8</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <spring-boot.version>2.3.12.RELEASE</spring-boot.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <!--fastjson-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.61</version>
        </dependency>
        <!--elasticsearch-->
        <dependency>
            <groupId>org.elasticsearch.client</groupId>
            <artifactId>elasticsearch-rest-high-level-client</artifactId>
            <version>7.6.1</version>
        </dependency>
        <dependency>
            <groupId>org.elasticsearch</groupId>
            <artifactId>elasticsearch</artifactId>
            <version>7.6.1</version>
        </dependency>
    </dependencies>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring-boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>${spring-boot.version}</version>
                <configuration>
                    <mainClass>com.example.elasticsearch.ElasticsearchApplication</mainClass>
                    <skip>true</skip>
                </configuration>
                <executions>
                    <execution>
                        <id>repackage</id>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

</project>

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<HttpHost> 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","remark": {
          "type": "text","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","keyword")
                                .endObject()
                            .endObject()
                        .endObject()
                        .startObject("remark")
                            .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","_version": 1,"_primary_term": 3,"found": true,"_source": {
        "address": "北京市","salary": 100
    }
}

更新文档信息

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

PUT http://localhost:9200/testindex/doc/hZo5_4oBFE0BmNy_GMUN
//请求
{
    "address": "北京市","name": "张三(改名字)","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("文档是否存在:{}",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,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": "低层员工","salary": 3000,"birthDate": "1990-11-11","createTime": "2019-11-11T08:18:00.000Z"
}

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":"北京市房山区","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":"北京市密云区","age":31,"salary":4800,"birthDate":"1988-07-20","createTime":"2019-05-29T09:00:00.000Z"} 
{"index":{"_index":"testindex","_type":"doc"}}
{"name":"赵六","address":"北京市通州区","age":32,"salary":6500,"birthDate":"1987-06-02","createTime":"2019-12-10T18:00:00.000Z"} 
{"index":{"_index":"testindex","_type":"doc"}}
{"name":"孙七","address":"北京市朝阳区","age":33,"salary":7000,"birthDate":"1986-04-15","createTime":"2019-06-06T13:00:00.000Z"} 
{"index":{"_index":"testindex","_type":"doc"}}
{"name":"周八","address":"北京市西城区","salary":5000,"birthDate":"1987-09-26","createTime":"2019-01-26T14:00:00.000Z"} 
{"index":{"_index":"testindex","_type":"doc"}}
{"name":"吴九","address":"北京市海淀区","salary":11000,"birthDate":"1989-11-25","createTime":"2019-09-07T13:34:00.000Z"} 
{"index":{"_index":"testindex","_type":"doc"}}
{"name":"郑十","address":"北京市东城区","age":29,"birthDate":"1990-12-25","createTime":"2019-03-06T12:08:00.000Z"} 
{"index":{"_index":"testindex","_type":"doc"}}
{"name":"萧十一","address":"北京市平谷区","salary":3300,"birthDate":"1990-11-11","createTime":"2019-03-10T08:17:00.000Z"} 
{"index":{"_index":"testindex","_type":"doc"}} 
{"name":"曹十二","address":"北京市怀柔区","salary":6800,"createTime":"2019-12-03T11:09:00.000Z"} 
{"index":{"_index":"testindex","_type":"doc"}}
{"name":"吴十三","address":"北京市延庆区","age":25,"birthDate":"1994-10-05","createTime":"2019-07-27T14:22:00.000Z"} 
{"index":{"_index":"testindex","_type":"doc"}}
{"name":"冯十四","birthDate":"1994-08-18","createTime":"2019-04-22T15:00:00.000Z"} 
{"index":{"_index":"testindex","_type":"doc"}}
{"name":"蒋十五","salary":2800,"createTime":"2019-06-13T10:00:00.000Z"} 
{"index":{"_index":"testindex","_type":"doc"}}
{"name":"苗十六","address":"北京市门头沟区","salary":11500,"createTime":"2019-11-11T18:00:00.000Z"} 
{"index":{"_index":"testindex","_type":"doc"}}
{"name":"鲁十七","address":"北京市石景山区","remark":"高员工","salary":9500,"createTime":"2019-06-06T14:00:00.000Z"} 
{"index":{"_index":"testindex","_type":"doc"}}
{"name":"沈十八","salary":8300,"birthDate":"1988-09-26","createTime":"2019-09-25T14:00:00.000Z"} 
{"index":{"_index":"testindex","_type":"doc"}}
{"name":"吕十九","salary":4500,"birthDate":"1988-11-25","createTime":"2019-09-22T13:34:00.000Z"} 
{"index":{"_index":"testindex","_type":"doc"}}
{"name":"丁二十","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,"skipped": 0,"hits": {
        "total": {
            "value": 2,"relation": "eq"
        },"max_score": 2.302585,"hits": [
            {
                "_index": "testindex","_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","_id": "5yDh-IoByPOFA_QWinlo","_source": {
                    "name": "蒋十五","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,"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,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,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,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,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,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,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,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,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;

    /**
     * 查询所有以 “三” 结尾的姓名
     * <p>
     * *:表示多个字符(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,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,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:{}",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:{}",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:{}",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<String,Long> 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<? extends Terms.Bucket> 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<String,Long> 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<? extends Range.Bucket> buckets = byCompanyAggregation.getBuckets();
                // 输出各个桶的内容
                log.info("-------------------------------------------");
                log.info("聚合信息:");
                for (Range.Bucket bucket : buckets) {
                    keyCountMap.put(bucket.getKeyAsString(),e);
        }
        return keyCountMap;
    }

    /**
     * 按照时间范围进行分桶,统计 1985-1990 年和 1990-1995 年出生的员工信息:
     */
    public Object aggrBucketDateRange() {
        Map<String,Long> 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<? extends Range.Bucket> buckets = byCompanyAggregation.getBuckets();
                // 输出各个桶的内容
                log.info("-------------------------------------------");
                log.info("聚合信息:");
                for (Range.Bucket bucket : buckets) {
                    keyCountMap.put(bucket.getKeyAsString(),e);
        }
        return keyCountMap;
    }

    /**
     * 按工资多少进行聚合分桶
     */
    public Object aggrBucketHistogram() {
        Map<String,Long> 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<? extends Histogram.Bucket> buckets = byCompanyAggregation.getBuckets();
                // 输出各个桶的内容
                log.info("-------------------------------------------");
                log.info("聚合信息:");
                for (Histogram.Bucket bucket : buckets) {
                    keyCountMap.put(bucket.getKeyAsString(),e);
        }
        return keyCountMap;
    }

    /**
     * 按出生日期进行分桶:
     */
    public Object aggrBucketDateHistogram() {
        Map<String,Long> 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<? extends Histogram.Bucket> buckets = byCompanyAggregation.getBuckets();
                // 输出各个桶的内容
                log.info("-------------------------------------------");
                log.info("聚合信息:");
                for (Histogram.Bucket bucket : buckets) {
                    keyCountMap.put(bucket.getKeyAsString(),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<String,Float> 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<? extends Terms.Bucket> 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

icon-default.png

https://download.csdn.net/download/asd051377305/88397087

2、项目源代码

基于SpringBoot+elasticsearch的操作项目

icon-default.png

https://download.csdn.net/download/asd051377305/88397090

 

原文地址:https://blog.csdn.net/asd051377305/article/details/133581804

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


文章浏览阅读774次,点赞24次,收藏16次。typescript项目中我们使用typings-for-css-modules-loader来替代css-loader实现css modules。1、typings-for-css-modules-loader加载器介绍 Webpack加载器,用作css-loader的替代产品,可动态生成CSS模块的TypeScript类型这句话是什么意思呢?就是编译时处理css文件...
文章浏览阅读784次。react router redux antd eslint prettier less axios_react+antd+redux+less
文章浏览阅读3.9k次,点赞5次,收藏11次。需要删除.security-7索引文件。把在第1步中的被注释的配置打开。之后就是按照提示输入密码。执行bin目录下的文件。_failed to authenticate user 'elastic' against
文章浏览阅读1.2k次,点赞23次,收藏24次。Centos 8 安装es_centos8 yum elasticsearch
文章浏览阅读3.2k次。设置完之后,数据会⾃动同步到其他节点。修改密码时,将第⼀步配置删除,然后重启。单独使⽤⼀个节点⽣成证书;执⾏设置⽤户名和密码的命令。执⾏完上⾯命令以后就可以在。⽂件,在⾥⾯添加如下内容。这个⽂件复制到其他节点下。其中⼀个节点设置密码即可。依次对每个账户设置密码。全部节点都要重启⼀遍。需要在配置⽂件中开启。个⽤户分别设置密码,⽬录下,证书⽂件名为。功能,并指定证书位置。_es设置账号和密码
文章浏览阅读1.9k次,点赞2次,收藏7次。针对多数据源写入的场景,可以借助MQ实现异步的多源写入,这种情况下各个源的写入逻辑互不干扰,不会由于单个数据源写入异常或缓慢影响其他数据源的写入,虽然整体写入的吞吐量增大了,但是由于MQ消费是异步消费,所以不适合实时业务场景。不易出现数据丢失问题,主要基于MQ消息的消费保障机制,比如ES宕机或者写入失败,还能重新消费MQ消息。针对这种情况,有数据强一致性要求的,就必须双写放到事务中来处理,而一旦用上事物,则性能下降更加明显。可能出现延时问题:MQ是异步消费模型,用户写入的数据不一定可以马上看到,造成延时。_mysql同步es
文章浏览阅读3.6w次,点赞48次,收藏44次。【程序员洲洲送书福利-第十九期】《C++ Core Guidelines解析》
文章浏览阅读1.3k次。当我们在开发Vue应用时,经常需要对表单进行校验,以确保用户输入的数据符合预期。Vue提供了一个强大的校验规则机制,通过定义rules规则,可以方便地对表单进行验证,并给出相应的错误提示。_vue ruler校验
文章浏览阅读2k次,点赞16次,收藏12次。Linux内核源码下载地址及方式_linux源码下载
文章浏览阅读1k次。这样在每天自动生成的索引skywalking_log_xxx就会使用上述模版来生成,timestamp会被设置成date类型。然后此时在–>索引管理–>kibana–>索引模式添加skywalking_log*索引时就会有时间字段了。在通过skywalking将日志收集到es后,由于skywalking收集的日志(skywalking_log索引)没有date类型的字段导致在es上再索引模式中没有时间范围的查询。skywalking收集的日志有时间戳字段timestamp,只是默认为long类型。_skywalking timestamp
文章浏览阅读937次,点赞18次,收藏21次。1.初始化git仓库,使用git int命令。2.添加文件到git仓库,两步走:2.1 使用命令,注意,可反复多次使用,添加多个文件;2.2 使用命令,完成。此笔记是我个人学习记录笔记,通过廖雪峰的笔记进行学习,用自己能理解的笔记记录下来,如果侵权,联系删。不存在任何盈利性质,单纯发布后,用于自己学习回顾。
文章浏览阅读786次,点赞8次,收藏7次。上述示例中的 origin 是远程仓库的名称,https://github.com/example/repository.git 是远程仓库的 URL,(fetch) 表示该远程仓库用于获取更新,(push) 表示该远程仓库用于推送更新。你可以选择在本地仓库创建与远程仓库分支对应的本地分支,也可以直接将本地仓库的分支推送到远程仓库的对应分支。将 替换为远程仓库的名称(例如 origin), 替换为要推送的本地分支的名称, 替换为要推送到的远程分支的名称。_git remote 智能切换仓库
文章浏览阅读1.5k次。配置eslint校验代码工具_eslint 实时校验
文章浏览阅读1.2k次,点赞28次,收藏26次。Git入门基础介绍,什么是Git,如何使用Git,以及Git的工作的基本原理
文章浏览阅读2.7k次。基于官方给出的几种不同环境不同的安装方式,本文将会选择在使用.zip文件在Windows上安装Elasticsearch在Linux或macOS上从存档文件安装ElasticsearchInstall Elasticsearch with Docker (此种方式待定)使用Docker安装Elasticsearch。_elasticsearch安装部署windows
文章浏览阅读3.3k次,点赞5次,收藏11次。【Linux驱动】内核模块编译 —— make modules 的使用(单模块编译、多模块编译)_make modules
文章浏览阅读1k次。docker启动es报错_max virtual memory areas vm.max_map_count [65530] is too low, increase to at
文章浏览阅读4.2k次,点赞2次,收藏6次。使用docker单机安装elasticsearch后再安装kibana时找不到es。_unable to retrieve version information from elasticsearch nodes. security_ex
文章浏览阅读1.1k次。日志处理对于任何现代IT系统都是关键部分,本教程专为新手设计,通过详细解释Logstash的三大核心组件,为您展示如何从零开始搭建强大的日志处理系统。您还将学习如何同步MySQL数据到Elasticsearch,并通过一个"Hello World"示例快速入门。无论您是完全的新手还是有一些基础,本教程都将引导您顺利掌握Logstash的基本操作和高级应用。_logstash mysql
文章浏览阅读1.1w次,点赞5次,收藏25次。执行这条指令之后,你的本地项目就与远程Git仓库建立了连接,你就可以开始对你的代码进行版本追踪和协作开发了。使用“git remote add origin”指令,可以轻松地将本地项目连接到远程Git仓库。git remote set-url origin 执行这条指令之后,Git就会将已经添加的名为“origin”的仓库删除。git remote add origin 其中,是你的远程Git仓库的网址。_git remote add origin