Elasticsearch_2. 与Spring Boot整合

作者: 小疯子 分类: Elasticsearch 发布时间: 2019-05-18 12:14

一、参考链接

SpringBoot整合Elasticsearch

Spring Data Elasticsearch是Spring Data项目下的一个子模块。查看 Spring Data的官网:http://projects.spring.io/spring-data/

关于Elasticsearch基本入门概念参见上一篇博文 Elasticsearch_1. 简单入门

二、配置

  1. Elasticsearch配置文件

    首先配置好Elasticsearch的相关信息,为后期在Spring Boot中的配置统一
    在安装包下的config/elasticsearch.yml配置如下内容(注意配置字段前面不要有空格,而且配置时的:冒号后面需要留有一个空格)

    # ======================== Elasticsearch Configuration =========================
    #
    # NOTE: Elasticsearch comes with reasonable defaults for most settings.
    # Before you set out to tweak and tune the configuration, make sure you
    # understand what are you trying to accomplish and the consequences.
    #
    # The primary way of configuring a node is via this file. This template lists
    # the most important settings you may want to configure for a production cluster.
    #
    # Please consult the documentation for further information on configuration options:
    # https://www.elastic.co/guide/en/elasticsearch/reference/index.html
    #
    # ---------------------------------- Cluster -----------------------------------
    #
    # Use a descriptive name for your cluster:
    #
    cluster.name: my-application
    #
    # ----------------------------------- Node ------------------------------------
    #
    # Use a descriptive name for the node:
    #
    node.name: node-1
    #
    # Add custom attributes to the node:
    #
    #node.attr.rack: r1
    #
    # ----------------------------------- Paths ------------------------------------
    # Path to directory where to store the data (separate multiple locations by comma):
    #
    #path.data: /path/to/data
    #
    # Path to log files:
    #
    #path.logs: /path/to/logs
    #
    # ----------------------------------- Memory -----------------------------------
    #
    # Lock the memory on startup:
    #
    #bootstrap.memory_lock: true
    #
    # Make sure that the heap size is set to about half the memory available
    # on the system and that the owner of the process is allowed to use this
    # limit.
    #
    # Elasticsearch performs poorly when the system is swapping the memory.
    #
    # ---------------------------------- Network -----------------------------------
    #
    # Set the bind address to a specific IP (IPv4 or IPv6):
    #
    network.host: localhost
    #192.168.0.1
    #
    # Set a custom port for HTTP:
    #
    http.port: 9200
    #
    # For more information, consult the network module documentation.
    # --------------------------------- Discovery ----------------------------------
    #
    # Pass an initial list of hosts to perform discovery when this node is started:
    # The default list of hosts is ["127.0.0.1", "[::1]"]
    #
    #discovery.seed_hosts: ["host1", "host2"]
    #
    # Bootstrap the cluster using an initial set of master-eligible nodes:
    #
    #cluster.initial_master_nodes: ["node-1", "node-2"]
    # For more information, consult the discovery and cluster formation module documentation.
    #
    # ---------------------------------- Gateway -----------------------------------
    #
    # Block initial recovery after a full cluster restart until N nodes are started:
    #
    #gateway.recover_after_nodes: 3
    #
    # For more information, consult the gateway module documentation.
    #
    # ---------------------------------- Various -----------------------------------
    #
    # Require explicit names when deleting indices:
    #
    #action.destructive_requires_name: true
    http.cors.enabled: true 
    http.cors.allow-origin: "*"
    node.master: true
    node.data: true
    
  2. Spring Boot项目中的pom.xml配置

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.0.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
        <elasticsearch.version>6.7.1</elasticsearch.version>
    </properties>
    
    
    <dependencies>
      <dependency>
         <groupId>org.springframework.data</groupId>
         <artifactId>spring-data-elasticsearch</artifactId>
      </dependency>
    </dependencies>

    目前我的spring-data-elasticsearch相关版本如下图所示:
    es01.png
    要注意,报了很多错误,错误的原因都是各种版本不匹配,可以看项目启动的错误日志,也可以看elasticsearch启动后的黑窗口报错情况。
    我报的错误有以下
    再说一下自己走的坑,报的各种错误,错误如下:

    (1)NoNodeAvailableException[None of the configured nodes are available: [{#transport#-1}{NqIEK9JVSzOk5LOfSY0MYg}{127.0.0.1}{127.0.0.1:9300}]
    这个错误原因在黑窗口提示如下,就是需要的是6.7.0以上的版本,但是springboot用的是6.4.2,所以我在上一步指定了elasticsearch.version为6.7.1

    es2.png
    (2)Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'elasticsearchClient' defined in class path resource
    和上面一样也是版本问题,之前pom.xml中用的不是spring-data-elasticsearch而是,spring-boot-starter-data-elasticsearch,而使用这个starter是有问题的,详细见完整教程:spring-boot-starter-data-elasticsearch整合elasticsearch 6.x
    新版本的SpringBoot 2的spring-boot-starter-data-elasticsearch中支持的Elasticsearch版本是2.X,
    但Elasticsearch实际上已经发展到6.5.X版本了,为了更好的使用Elasticsearch的新特性,
    所以弃用了spring-boot-starter-data-elasticsearch依赖,而改为直接使用Spring-data-elasticsearch,大致意思:Spring boot 2的spring-boot-starter-data-elasticsearch中支持的Elasticsearch 2.X版本,需要转向spring-data-elasticsearch。

  3. application.yml中进行elasticsearch的集群、host和port等相关配置
    spring:
      data:
        elasticsearch:
          cluster-name: my-application
          cluster-nodes: localhost:9300
          repositories:
            enabled: true

    注意端口是9300,虽然在elasticsearch的配置中是9200,但是解决错误的时候总让我改成9300
    es启动监听两个端口,9300和9200
    9300端口是使用tcp客户端连接使用的端口;
    9200端口是通过http协议连接es使用的端口;

三、项目使用

 

  1. 创建索引和映射

    创建一个机构表和对应的实体类Organization.
    Spring Data通过注解来声明字段的映射属性,有下面的三个注解:

    • @Document 作用在类,标记实体类为文档对象,一般有两个属性
      • indexName:对应索引库名称
      • type:对应在索引库中的类型
      • shards:分片数量,默认5
      • replicas:副本数量,默认1
    • @Id 作用在成员变量,标记一个字段作为id主键
    • @Field 作用在成员变量,标记为文档的字段,并指定字段映射属性:
      • type:字段类型,是枚举:FieldType,可以是text、long、short、date、integer、object等
        • text:存储数据时候,会自动分词,并生成索引
        • keyword:存储数据时候,不会分词建立索引
        • Numerical:数值类型,分两类
          • 基本数据类型:long、interger、short、byte、double、float、half_float
          • 浮点数的高精度类型:scaled_float
            • 需要指定一个精度因子,比如10或100。elasticsearch会把真实值乘以这个因子后存储,取出时再还原。
        • Date:日期类型
          • elasticsearch可以对日期格式化为字符串存储,但是建议我们存储为毫秒值,存储为long,节省空间。
      • index:是否索引,布尔类型,默认是true
      • store:是否存储,布尔类型,默认是false
      • analyzer:分词器名称,这里的ik_max_word即使用ik分词器
  2. 示例

    (一)实体类

    @5+0%2Fdocs%2Fapi+Entity">Entity
    @Data
    @Table(name = "t_organization")
    @5+0%2Fdocs%2Fapi+Document">Document(indexName = "organization", type = "orga_doc",shards = 1, replicas = 0)  // 一个分片,0个
    public class Organization extends MyBaseEntity {
         /**
    	 * @Description: @Id注解必须是springframework包下的
    	 * org.springframework.data.annotation.Id						
    	 *@Author: https://blog.csdn.net/chen_2890
         */
        @Id
        private 1.5.0/docs/api/java/lang/Long.html">Long id;
       @1.5.0/docs/api/java/lang/reflect/Field.html">Field(type = FieldType.1.5.0/docs/api/org/w3c/dom/Text.html">Text, analyzer = "ik_max_word")
        private 1.5.0/docs/api/java/lang/String.html">String name;
     
        @1.5.0/docs/api/java/lang/reflect/Field.html">Field(type = FieldType.Keyword)
        private 1.5.0/docs/api/java/lang/String.html">String code;
     
     
        @1.5.0/docs/api/java/lang/reflect/Field.html">Field(type = FieldType.Keyword)
        private 1.5.0/docs/api/java/lang/String.html">String  address;
    }

    (二)索引相关

    (1)创建索引
    然后加一个单元测试进行创建索引的操作,代码如下

    @RunWith(SpringRunner.class)
    @SpringBootTest
    @Slf4j
    public class ElasticsearchUtilTest {
     
        @Autowired
        private ElasticsearchTemplate elasticsearchTemplate;
     
        /* 这个一定要加上,要不然报这个错误java.lang.IllegalStateException: availableProcessors is already set to [4], rejecting [4]*/
        static {
            1.5.0/docs/api/java/lang/System.html">System.setProperty("es.set.netty.runtime.available.processors", "false");
        }
     
        /**
         * 创建索引,根据Organization.class类的@Document注解信息来创建
         */
        @Test
        public void testCreateIndex(){
            log.info("*****************es_config*************************");
            log.info("es.set.netty.runtime.available.processors:{}", 1.5.0/docs/api/java/lang/System.html">System.getProperty("es.set.netty.runtime.available.processors"));
            log.info("***************************************************");
            elasticsearchTemplate.createIndex(Organization.class);
        }
     
    }

    执行结果如下:

    2019-05-18 10:16:52.118 INFO 12920 --- [ main] c.x.p.c.utils.ElasticsearchUtilTest : *****************es_config*************************
    2019-05-18 10:16:52.118 INFO 12920 --- [ main] c.x.p.c.utils.ElasticsearchUtilTest : es.set.netty.runtime.available.processors:false
    2019-05-18 10:16:52.118 INFO 12920 --- [ main] c.x.p.c.utils.ElasticsearchUtilTest : ***************************************************

    注意如果不配置es.set.netty.runtime.available.processors会报错如下:

    java.lang.IllegalStateException: availableProcessors is already set to [4], rejecting [4]
    at io.netty.util.NettyRuntime$AvailableProcessorsHolder.setAvailableProcessors(NettyRuntime.java:51)
    at io.netty.util.NettyRuntime.setAvailableProcessors(NettyRuntime.java:87)
    at org.elasticsearch.transport.netty4.Netty4Utils.setAvailableProcessors(Netty4Utils.java:87)
    at org.elasticsearch.transport.netty4.Netty4Transport.<init>(Netty4Transport.java:115)
    at org.elasticsearch.transport.Netty4Plugin.lambda$getTransports$0(Netty4Plugin.java:84)
    at org.elasticsearch.client.transport.TransportClient.buildTemplate(TransportClient.java:176)
    at org.elasticsearch.client.transport.TransportClient.<init>(TransportClient.java:262)
    at org.elasticsearch.transport.client.PreBuiltTransportClient.<init>(PreBuiltTransportClient.java:128)

    出现原因和解决方法
    因为elasticsearch的transportclient包中,会引入netty进行通信。由于项目中其他包也会自带有netty,或许是我的rabbitmq带着吧。两种解决方法,第一种是在pom中直接引用netty

    <dependency>

    <groupId>io.netty</groupId>
    <artifactId>netty-all</artifactId>
    <version>4.1.13.Final</version>
    </dependency>(没试过)
    第二种就是代码中写的,设置环境变量,解决es的netty和Netty服务本身不兼容问题,初始化transportClient方法前添加
    System.setProperty("es.set.netty.runtime.available.processors", "false");
    如果闲着就可以到Kibana中的dev tools中查看所有索引,看看organiztion索引是否存在,执行命令是:

    #查看所有索引信息
    GET /_cat/indices?v&pretty
    结果如下:

    es3.png

    
    

    关于索引相关的操作参见官方: 索引管理
    (2)删除索引

    /**
     * 删除索引
     */
    @Test
    public void deleteIndex(){
        elasticsearchTemplate.deleteIndex(Organization.class);
    }

    在到kibana中运行查看索引信息的命令,如下发现没有organization了
    es4.png

    (三)文档相关

    (1)新增文档对象
    a. Repository接口
    Spring Data 的强大之处,就在于你不用写任何DAO处理,自动根据方法名或类的信息进行CRUD操作。只要你定义一个接口,然后继承Repository提供的一些子接口,就能具备各种基本的CRUD功能。

    来看下Repository的继承关系:

    es5.png
    我们看到有一个ElasticsearchCrudRepository接口:
    所以,我们只需要定义接口,然后继承它就OK了。

    /**
     * @Author: Xiaofeng
     * @Date: 2019/5/18 10:55
     * @Description: 定义OrganizationRepository接口
     * @Param:
     *      Organization: 为实体类
     *      Long: 为Organization实体类中主键的数据类型
     */
    public interface OrganizationRepository extends ElasticsearchRepository<Organization, Long> {
    }

    b. 新增一个文档对象

    /**
     * 新增一个对象(也就是文档)
     */
    @Test
    public void insert(){
        Organization organization = new Organization();
        organization.setName("hehehe");
        organization.setCode("ddd");
        organization.setAddress("ioioio");
     
        organizationRepository.save(organization);
    }

    然后到kibana中执行命令查看名下的文档

    #获取organization索引名下的文档
    GET /organization/_search

    es6.png
    (2)批量新增文档对象

    @Test
    public void insertList(){
        List<Organization> list = new ArrayList<>();
        list.add(new Organization(11L, "aa1", "bb1","cc1"));
        list.add(new Organization(12L, "aa2", "bb2","cc2"));
        organizationRepository.saveAll(list);
    }

    (3)修改
    elasticsearch中本没有修改,它的修改原理是该是先删除在新增
    修改和新增是同一个接口,区分的依据就是id。

    @Test
    public void update(){
        Organization organization = new Organization(11L, "aa1111", "bb1","cc1");
        organizationRepository.save(organization);
    }

    (四)查询相关

    (1)基本查询
    ElasticsearchRepository提供了一些基本的查询方法:
    es7.png
    查询所有示例:

    /**
     * 查询所有
     */
    @Test
    public void queryAll() {
        // 对某字段排序查找所有
        Iterable<Organization> list = organizationRepository.findAll(Sort.by("code").ascending());
        for (Organization item : list) {
            System.out.println(item);
        }
    }

    报错:NotSerializableExceptionWrapper[: Fielddata is disabled on text fields by default. Set fielddata=true on [code] in order to load fielddata in memory by uninverting the inverted index.
    这个异常是是由于对text类型的字段进行排序造成的。也就是上面的code是text类型的不能排序,要么删了要么更改其类型(没有深入研究):
    核心数据类型(Core datatypes)

    string

    text:全文检索需要分词的类型。

    keyword:精确值。合适分组排序。

    查看索引字段映射信息如下命令所示:
    # 查看映射类型
    GET /organization/_mapping
    相关官网链接:动态映射
    (2)自定义方法
    Spring Data 的另一个强大功能,是根据方法名称自动实现功能。

    比如:你的方法名叫做:findByTitle,那么它就知道你是根据title查询,然后自动帮你完成,无需写实现类。

    当然,方法名称要符合一定的约定:

    Keyword Sample
    And findByNameAndPrice
    Or findByNameOrPrice
    Is findByName
    Not findByNameNot
    Between findByPriceBetween
    LessThanEqual findByPriceLessThan
    GreaterThanEqual findByPriceGreaterThan
    Before findByPriceBefore
    After findByPriceAfter
    Like findByNameLike
    StartingWith findByNameStartingWith
    EndingWith findByNameEndingWith
    Contains/Containing findByNameContaining
    In findByNameIn(Collection<String>names)
    NotIn findByNameNotIn(Collection<String>names)
    Near findByStoreNear
    True findByAvailableTrue
    False findByAvailableFalse
    OrderBy findByAvailableTrueOrderByNameDesc

    (3)自定义查询
    最基本的matchQuery

    /**
     * matchQuery底层采用的是词条匹配查询
     */
    @Test
    public void testMatchQuery() {
        // 构建查询条件
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        // 添加基本粉刺查询
        queryBuilder.withQuery(QueryBuilders.matchQuery("name", "uu"));
        // 搜索,获取结果
        Page<Organization> organizations = this.organizationRepository.search(queryBuilder.build());
        //总条数
        long total = organizations.getTotalElements();
        1.5.0/docs/api/java/lang/System.html">System.out.println("total = " + total);
        for (Organization organization : organizations) {
            1.5.0/docs/api/java/lang/System.html">System.out.println(organization);
        }
    }
    • NativeSearchQueryBuilder:Spring提供的一个查询条件构建器,帮助构建json格式的请求体
    • QueryBuilders.matchQuery(“title”, “小米手机”):利用QueryBuilders来生成一个查询。QueryBuilders提供了大量的静态方法,用于生成各种不同类型的查询:
      在这里插入图片描述
    • Page<item>:默认是分页查询,因此返回的是一个分页的结果对象,包含属性:
      • totalElements:总条数
      • totalPages:总页数
      • Iterator:迭代器,本身实现了Iterator接口,因此可直接迭代得到当前页的数据
      • 其它属性:
        在这里插入图片描述

    (4)分页查询
    利用NativeSearchQueryBuilder可以方便的实现分页:

    /**
     * 分页查询
     */
    @Test
    public void searchByPage(){
        // 构建查询条件
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        // 添加基本分词查询
        queryBuilder.withQuery(QueryBuilders.termQuery("address", "cc"));
        // 分页:
        int page = 0;
        int size = 2;
        queryBuilder.withPageable(PageRequest.of(page,size));
        // 搜索,获取结果
        Page<Organization> organizations = this.organizationRepository.search(queryBuilder.build());
        // 总条数
        long total = organizations.getTotalElements();
        1.5.0/docs/api/java/lang/System.html">System.out.println("总条数 = " + total);
        // 总页数
        1.5.0/docs/api/java/lang/System.html">System.out.println("总页数 = " + organizations.getTotalPages());
        // 当前页
        1.5.0/docs/api/java/lang/System.html">System.out.println("当前页:" + organizations.getNumber());
        // 每页大小
        1.5.0/docs/api/java/lang/System.html">System.out.println("每页大小:" + organizations.getSize());
     
        for (Organization organization : organizations) {
            1.5.0/docs/api/java/lang/System.html">System.out.println(organization);
        }
    }

    (5)排序
    排序也通用通过NativeSearchQueryBuilder完成:

    /**
     * @Description:排序查询
     */
    @Test
    public void searchAndSort(){
        // 构建查询条件
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        // 添加基本分词查询
        queryBuilder.withQuery(QueryBuilders.termQuery("address", "cc"));
     
        // 排序
        queryBuilder.withSort(SortBuilders.fieldSort("code").order(SortOrder.ASC));
     
        // 搜索,获取结果
        Page<Organization> organizations = this.organizationRepository.search(queryBuilder.build());
        // 总条数
        long total = organizations.getTotalElements();
        1.5.0/docs/api/java/lang/System.html">System.out.println("总条数 = " + total);
     
        for (Organization organization : organizations) {
            1.5.0/docs/api/java/lang/System.html">System.out.println(organization);
        }
    }

    (五)聚合

    聚合可以让我们极其方便的实现对数据的统计、分析。例如:

    • 什么品牌的手机最受欢迎?
    • 这些手机的平均价格、最高价格、最低价格?
    • 这些手机每月的销售情况如何?

    实现这些统计功能的比数据库的sql要方便的多,而且查询速度非常快,可以实现近实时搜索效果。
    (1)聚合基本概念

    Elasticsearch中的聚合,包含多种类型,最常用的两种,一个叫桶,一个叫度量:

    桶(bucket)

    桶的作用,是按照某种方式对数据进行分组,每一组数据在ES中称为一个桶,例如我们根据国籍对人划分,可以得到中国桶、英国桶,日本桶……或者我们按照年龄段对人进行划分:010,1020,2030,3040等。

    Elasticsearch中提供的划分桶的方式有很多:

    *Date Histogram Aggregation:根据日期阶梯分组,例如给定阶梯为周,会自动每周分为一组
    *Histogram Aggregation:根据数值阶梯分组,与日期类似
    *Terms Aggregation:根据词条内容分组,词条内容完全匹配的为一组
    *Range Aggregation:数值和日期的范围分组,指定开始和结束,然后按段分组
    ……
    综上所述,我们发现bucket aggregations 只负责对数据进行分组,并不进行计算,因此往往bucket中往往会嵌套另一种聚合:metrics aggregations即度量

    度量(metrics)
    分组完成以后,我们一般会对组中的数据进行聚合运算,例如求平均值、最大、最小、求和等,这些在ES中称为度量

    比较常用的一些度量聚合方式:

    *Avg Aggregation:求平均值
    *Max Aggregation:求最大值
    *Min Aggregation:求最小值
    *Percentiles Aggregation:求百分比
    *Stats Aggregation:同时返回avg、max、min、sum、count等
    *Sum Aggregation:求和
    *Top hits Aggregation:求前几
    *Value Count Aggregation:求总数
    *……
    注意:在ES中,需要进行聚合、排序、过滤的字段其处理方式比较特殊,因此不能被分词。这里我们将color和make这两个文字类型的字段设置为keyword类型,这个类型不会被分词,将来就可以参与聚合

    (2)聚合为桶
    桶就是分组,比如这里我们按照品牌brand进行分组:

    /**
     * 按照code进行分组
     */
    public void testAgg(){
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        // 不查询任何结果
        queryBuilder.withSourceFilter(new FetchSourceFilter(new 1.5.0/docs/api/java/lang/String.html">String[]{""}, null));
        // 1、添加一个新的聚合,聚合类型为terms,聚合名称为codes,聚合字段为code
        queryBuilder.addAggregation(
                AggregationBuilders.terms("codes").field("code"));
        // 2、查询,需要把结果强转为AggregatedPage类型
        AggregatedPage<Organization> aggPage = (AggregatedPage<Organization>) this.organizationRepository.search(queryBuilder.build());
        // 3、解析
        // 3.1、从结果中取出名为codes的那个聚合,
        // 因为是利用String类型字段来进行的term聚合,所以结果要强转为StringTerm类型
        StringTerms agg = (StringTerms)aggPage.getAggregation("codes");
        // 3.2、获取桶
        List<StringTerms.Bucket> buckets = agg.getBuckets();
        // 3.3、遍历
        for (StringTerms.Bucket bucket : buckets) {
            // 3.4、获取桶中的key,即品牌名称
            1.5.0/docs/api/java/lang/System.html">System.out.println(bucket.getKeyAsString());
            // 3.5、获取桶中的文档数量
            1.5.0/docs/api/java/lang/System.html">System.out.println(bucket.getDocCount());
        }
    }

    关键api:

    • AggregationBuilders:聚合的构建工厂类。所有聚合都由这个类来构建,看看他的静态方法:
      在这里插入图片描述
    1)统计某个字段的数量
    ValueCountBuilder vcb= AggregationBuilders.count("count_uid").field("uid");
    (2)去重统计某个字段的数量(有少量误差)
    CardinalityBuilder cb= AggregationBuilders.cardinality("distinct_count_uid").field("uid");
    (3)聚合过滤
    FilterAggregationBuilder fab= AggregationBuilders.filter("uid_filter").filter(QueryBuilders.queryStringQuery("uid:001"));
    (4)按某个字段分组
    TermsBuilder tb= AggregationBuilders.terms("group_name").field("name");
    (5)求和
    SumBuilder sumBuilder= AggregationBuilders.sum("sum_price").field("price");
    (6)求平均
    AvgBuilder ab= AggregationBuilders.avg("avg_price").field("price");
    (7)求最大值
    MaxBuilder mb= AggregationBuilders.max("max_price").field("price");
    (8)求最小值
    MinBuilder min= AggregationBuilders.min("min_price").field("price");
    (9)按日期间隔分组
    DateHistogramBuilder dhb= AggregationBuilders.dateHistogram("dh").field("date");
    (10)获取聚合里面的结果
    TopHitsBuilder thb= AggregationBuilders.topHits("top_result");
    (11)嵌套的聚合
    NestedBuilder nb= AggregationBuilders.nested("negsted_path").path("quests");
    (12)反转嵌套
    AggregationBuilders.reverseNested("res_negsted").path("kps ");
    • AggregatedPage:聚合查询的结果类。它是Page<T>的子接口:
      在这里插入图片描述
      AggregatedPagePage功能的基础上,拓展了与聚合相关的功能,它其实就是对聚合结果的一种封装。
      在这里插入图片描述
      而返回的结果都是Aggregation类型对象,不过根据字段类型不同,又有不同的子类表示
      在这里插入图片描述

    (3)嵌套聚合,求平均值

    /**
     * 嵌套聚合,求平均值 懒得改直接用大神写的代码
     */
    @Test
    public void testSubAgg(){
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        // 不查询任何结果
        queryBuilder.withSourceFilter(new FetchSourceFilter(new 1.5.0/docs/api/java/lang/String.html">String[]{""}, null));
        // 1、添加一个新的聚合,聚合类型为terms,聚合名称为brands,聚合字段为brand
        queryBuilder.addAggregation(
                AggregationBuilders.terms("brands").field("brand")
                        .subAggregation(AggregationBuilders.avg("priceAvg").field("price")) // 在品牌聚合桶内进行嵌套聚合,求平均值
        );
        // 2、查询,需要把结果强转为AggregatedPage类型
        AggregatedPage<Organization> aggPage = (AggregatedPage<Organization>) this.organizationRepository.search(queryBuilder.build());
        // 3、解析
        // 3.1、从结果中取出名为brands的那个聚合,
        // 因为是利用String类型字段来进行的term聚合,所以结果要强转为StringTerm类型
        StringTerms agg = (StringTerms) aggPage.getAggregation("brands");
        // 3.2、获取桶
        List<StringTerms.Bucket> buckets = agg.getBuckets();
        // 3.3、遍历
        for (StringTerms.Bucket bucket : buckets) {
            // 3.4、获取桶中的key,即品牌名称  3.5、获取桶中的文档数量
            1.5.0/docs/api/java/lang/System.html">System.out.println(bucket.getKeyAsString() + ",共" + bucket.getDocCount() + "台");
     
            // 3.6.获取子聚合结果:
            InternalAvg avg = (InternalAvg) bucket.getAggregations().asMap().get("priceAvg");
            1.5.0/docs/api/java/lang/System.html">System.out.println("平均售价:" + avg.getValue());
        }
    }
0