Spring boot集成Elasticserarch使用教程

  • Post author:
  • Post category:其他




一、简介

在Elasticsearch中,文档归属于一种类型(type),而这些类型存在于索引(index)中,类比传统关系型数据库:

Elasticsearch集群可以包含多个索引(indices)(数据库),每一个索引可以包含多个类型(types)(表),每一个类型包含多个文档(documents)(行),然后每个文档包含多个字段(Fields)(列)。


接入方式

使用spring-boot中的spring-data-elasticsearch,可以使用两种内置客户端接入:

1、节点客户端(node client):

配置文件中设置为local:false,节点客户端以无数据节点(node-master或node-client)身份加入集群,换言之,它自己不存储任何数据,但是它知道数据在集群中的具体位置,并且能够直接转发请求到对应的节点上。

2、传输客户端(Transport client):

配置文件中设置为local:true,这个更轻量的传输客户端能够发送请求到远程集群。它自己不加入集群,只是简单转发请求给集群中的节点。



二、项目依赖

代码中引入pom配置

<dependency>  
  <groupId>org.springframework.boot</groupId>  
  <artifactId>spring-boot-starter-data-elasticsearch</artifactId>  
</dependency>

配置文件中配置es的地址及账号密码

spring.elasticsearch.rest.uris=ip:port
spring.elasticsearch.rest.username=XX
spring.elasticsearch.rest.password=XX



三、对象中的注解



1.对象创建示例

@Document(indexName = "XX")
@Data
public class EsBillInfoEnd implements Serializable {
    @Id
    private String id;
    @Field("enterName")
    private String name;
    private String payName;
}



2.对象中的注解



2.1 @Document注解

应用于类级别,表示该类是映射到数据库的候选.里面包含的重要属性:

indexName:存储该实体的索引名称。

createIndex: 标记是否在存储库引导时创建索引。默认值为true。Spring Data Elasticsearch 将在应用程序启动时引导存储库支持期间检查@Document注释定义的索引是否存在。如果它不存在,将创建索引,并将从实体的注释派生的映射写入新创建的索引。

versionType: 版本管理的配置。默认值为EXTERNAL。



2.2 @Id注解

应用于字段级别以标记用于标识目的的字段。



2.3 @Transient注解

默认情况下,所有字段在存储或检索时都映射到文档,此注释不包括该字段。



2.4 @PersistenceConstructor注解

标记给定的构造函数 – 即使是受包保护的构造函数 – 在从数据库实例化对象时使用。构造函数参数按名称映射到检索到的文档中的键值。



2.5 @Field注解

应用于字段级别,定义字段的属性,里面包含的重要属性:

name: 字段名称,如果未设置,则使用 Java 字段名称。

type:字段类型。

format:一种或多种内置日期格式。

pattern:一种或多种自定义日期格式。

store: 标志是否应将原始字段值存储在 Elasticsearch 中,默认值为false。

analyzer,searchAnalyzer,normalizer用于指定自定义分析器和规范器。



2.6 @GeoPoint注解

将字段标记为geo_point地理位置数据类型。如果字段是GeoPoint类的实例,则可以省略。



四、代码使用

Elasticsearch 模块支持所有基本查询构建功能,如字符串查询、本地搜索查询、基于条件的查询或从方法名称派生的查询,通过建造查询对象实现复杂查询。


上部分代码为Java使用代码,下部分代码为Elasticsearch json查询语句。



1.根据方法名称创建查询

关键词 描述
find…By, read…By, get…By, query…By, search…By,stream…By 通用查询方法通常返回存储库类型、Collection或Streamable子类型或结果包装器,例如Page,GeoResults或任何其他特定于商店的结果包装器。可用作findBy…,findMyDomainTypeBy…或与其他关键字结合使用。
exists…By 通常返回boolean结果。
count…By 计数返回数字结果。
delete…By, remove…By 删除查询方法返回无结果 ( void) 或删除计数。
…First…, …Top… 将查询结果限制为第一个结果。此关键字可以出现在主题的find(和其他关键字)和之间的任何位置by。
…Distinct… 使用不同的查询仅返回唯一的结果。查阅特定于商店的文档是否支持该功能。此关键字可以出现在主题的find(和其他关键字)和之间的任何位置by。
interface BookRepository extends Repository<Book, String> {
        List<Book> findByNameAndPrice(String name, Integer price);
}
{
    "query": {
        "bool" : {
            "must" : [
                { "query_string" : { "query" : "?", "fields" : [ "name" ] } },
                { "query_string" : { "query" : "?", "fields" : [ "price" ] } }
            ]
        }
    }
}



2.使用JAVA API查询

  • 首先建造一个查询对象(下文中的操作步骤都基于此查询对象)
NativeSearchQueryBuilder nativeSearchQuery = new NativeSearchQueryBuilder();

NativeSearchQueryBuilder :用于建造一个NativeSearchQuery查询对象



2.1 简单查询

常用的SQL运算符和聚合函数对应的ES Builder:

Sql element Aggregation Type Code to build
AND BoolQueryBuilder QueryBuilders.boolQuery().must().add(sub QueryBuilder)
OR BoolQueryBuilder QueryBuilders.boolQuery().should().add(sub QueryBuilder)
NOT BoolQueryBuilder QueryBuilders.boolQuery().mustNot().add(sub QueryBuilder)
= BoolQueryBuilder QueryBuilders.termQuery(fieldName, value)
IN BoolQueryBuilder QueryBuilders.termsQuery(fieldName, values)
LIKE BoolQueryBuilder QueryBuilders.wildcardQuery(fieldName, value)
> BoolQueryBuilder QueryBuilders.rangeQuery(fieldName).gt(value)
>= BoolQueryBuilder QueryBuilders.rangeQuery(fieldName).gte(value)
< BoolQueryBuilder QueryBuilders.rangeQuery(fieldName).lt(value)
<= BoolQueryBuilder QueryBuilders.rangeQuery(fieldName).lte(value)

QueryStringQuery 支持在查询字符串中通过 AND OR NOT 进行布尔运算,同时也支持 +(must) 和 -(must not),通过指定多个查询字段以及复杂的布尔运算,我们可以精确的获取文档数据。

查询时字段名加.keyword则为精确查询,不加则默认分词查找



2.1.1 字段包含XX(queryStringQuery termQuery boolQuery)
  • match和term差别对比:

match在匹配时会对所查找的关键词进行分词,然后按分词匹配查找,而term会直接对关键词进行查找。一般模糊查找的时候,多用match,而精确查找时可以使用term。

  • bool查询包含四种操作符,分别是must,should,must_not,query。它们均是一种数组,数组里面是对应的判断条件:

must: 必须匹配,与and等价。贡献算分

must_not:必须不匹配,与not等价,常过滤子句用,但不贡献算分

should: 选择性匹配,至少满足一条,与 OR 等价。贡献算分

filter: 过滤子句,必须匹配,但不贡献算分

//查询enterName字段中包含台州医院的数据
nativeSearchQuery.withQuery(QueryBuilders.queryStringQuery("台州医院").defaultField("enterName"));
或者
nativeSearchQuery.withQuery(QueryBuilders.termQuery("enterName","台州医院"));
或者
nativeSearchQuery.withQuery(QueryBuilders.boolQuery().must(QueryBuilders.matchQuery("enterName","台州医院")));
GET dc-elk-bill-info-end-2021.06/_search
{
  "query": {
    "query_string": {
      "query": "台州医院",
      "fields": [
        "enterName"
      ]
    }
  }
}
或者
GET dc-elk-bill-info-end-2021.06/_search
{
  "query": {
    "term": {
      "enterName": "台州医院"
    }
  }
}
或者
GET dc-elk-bill-info-end-2021.06/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "term": {
            "enterName": "台州医院"
          }
        }
      ]
    }
  }
}


2.1.2 多字段包含XX(queryStringQuery)
//查询enterName字段中包含 台州医院 或者payName字段中包含 台州医院 的数据(分词查询)
nativeSearchQuery.withQuery(QueryBuilders.queryStringQuery("台州医院").field("enterName").field("payName"));
GET dc-elk-bill-info-end-2021.06/_search
{
  "query": {
    "query_string": {
      "query": "台州医院",
      "fields": [
        "enterName",
        "payName"
      ]
    }
  }
}


2.1.3 含有XX且不含有YY(queryStringQuery)
//查询enterName字段中含有医院且不含有台州的数据
nativeSearchQuery.withQuery(QueryBuilders.queryStringQuery("+医院 -台州").defaultField("enterName"));


2.1.4 含有XX或者不含有YY(simpleQueryStringQuery)
//查询enterName字段中含有医院或者不含有台州的数据
nativeSearchQuery.withQuery(QueryBuilders.simpleQueryStringQuery("+医院 -台州").field("enterName"));
GET dc-elk-bill-info-end-2021.06/_search
{
  "query": {
    "query_string": {
      "query": "(台州 or 医院)",
      "fields": [
        "enterName"
      ]
    }
  }
}



2.2 模糊查询



2.2.1 左右模糊查询(fuzzyQuery matchPhraseQuery)
//左右模糊查询,其中fuzziness的参数作用是在查询时,es动态的将查询关键词前后增加或者删除一个词,然后进行匹配
QueryBuilders.fuzzyQuery("enterName", "台州医院").fuzziness(Fuzziness.ONE)
或者
QueryBuilders.matchPhraseQuery("enterName","台州医院");
  • 说明:

fuzzy和match_phrase的区别

1.fuzzy是词/项级别的模糊匹配,match_phrase是基于短语级别的

例如对于英文(standard分析器)来说”dog cat bird”来说”dog”就是一个词/词项,而”dog cat”就是一个短语,因此作用范围不一样

2.fuzzy是基于莱文斯坦距离的,所以fuzzy是可以容错的例如你输入”dcg” 你也可以匹配到”dog cat bird”,但是这里注意的是你的查询只能是单词条的查询,不能”dcg cat”,如果你需要查询短语里面的拼写错误,可以使用match的fuzziness参数,match_phrase是不允许出现不存在的词条的。

GET dc-elk-bill-info-end-2021.06/_search
{
  "query": {
    "fuzzy": {
      "enterName": {
        "value": "台州医院",
        "fuzziness": 1
      }
    }
  }
}
或者
GET dc-elk-bill-info-end-2021.06/_search
{
  "query": {
    "match_phrase": {
      "enterName": "台州医院"
    }
  }
}


2.2.2 前缀查询(prefixQuery)
//前缀查询,查询title中以“台州医院”为前缀的document;
QueryBuilders.prefixQuery("enterName", "台州医院")
GET dc-elk-bill-info-end-2021.06/_search
{
  "query": {
    "prefix": {
      "enterName": {
        "value": "台州医院"
      }
    }
  }
}


2.2.3 通配符查询(wildcardQuery)
//通配符查询,支持*和?,?表示单个字符;注意不建议将通配符作为前缀,否则导致查询很慢
QueryBuilders.wildcardQuery("enterName", "台*院")
QueryBuilders.wildcardQuery("enterName", "台?院")
GET dc-elk-bill-info-end-2021.06/_search
{
  "query": {
    "wildcard": {
      "enterName": {
        "value": "台*院"
      }
    }
  }
}



2.3 范围查询

  • 说明:

include_lower表示是否包含边界最小值(true表示包含),include_upper表示是否包含边界最大值(true表示包含,false表示不包含)

gte:大于或等于

gt: 大于

lte:小于或等于

lt:小于

其中 from to 与 gte lt 等查询无明显区别

当只有一个查询条件时,查询参数可直接放入query中,多条查询条件放入bool中



2.3.1 闭区间查询(rangeQuery)
//闭区间查询,查询2021-06-23之间的数据
nativeSearchQuery.withQuery(QueryBuilders.rangeQuery("@timestamp").from("2021-06-23T00:00:00Z").to("2021-06-23T23:59:59Z").includeLower(true).includeUpper(false));
或者
nativeSearchQuery.withQuery(QueryBuilders.boolQuery().must(QueryBuilders.rangeQuery("@timestamp").gte("2021-06-23T00:00:00Z").lt("2021-06-23T23:59:59Z").includeLower(true).includeUpper(false)));
GET dc-elk-bill-info-end-2021.06/_search
{
  "query": {
    "range": {
      "@timestamp": {
        "from": "2021-06-23T00:00:00Z",
        "to": "2021-06-23T23:59:59Z",
        "include_lower": true,
        "include_upper": false,
        "boost": 1
      }
    }
  }
}
或者
GET dc-elk-bill-info-end-2021.06/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "range": {
            "@timestamp": {
              "gte": "2021-06-23T00:00:00Z",
              "lt": "2021-06-23T23:59:59Z",
              "include_lower": true,
              "include_upper": false,
              "boost": 1
            }
          }
        }
      ]
    }
  }
}


2.3.2 开区间查询\大于\大于等于\小于\小于等于
// 开区间查询,默认是true,也就是包含
QueryBuilders.rangeQuery("fieldName").from("fieldValue1").to("fieldValue2").includeUpper(false).includeLower(false);
//大于
QueryBuilders.rangeQuery("fieldName").gt("fieldValue");
//大于等于
QueryBuilders.rangeQuery("fieldName").gte("fieldValue");
//小于
QueryBuilders.rangeQuery("fieldName").lt("fieldValue");
//小于等于
QueryBuilders.rangeQuery("fieldName").lte("fieldValue");


2.3.3 分页(PageRequest)
nativeSearchQuery.withPageable(PageRequest.of(0, 5));

ES默认返回的条数值为10,如果想改变返回的条数值,可以指定size的大小,也可以指定from的起始位置,也就好比mysql中的pageIndex,size是pageSize,对应于mysql中的limit 0,5,则在ES中是from 0 to 5

GET dc-elk-bill-info-end-2021.06/_search
{
  "from": 0,
  "size": 5
}


2.3.4 排序(SortBuilders)
nativeSearchQuery.withSort(SortBuilders.fieldSort("@timestamp").order(SortOrder.ASC));
  • sort表示排序,ES提供了三种排序方式:

①:按照文档的得分来排序,ES会自动根据查询的条件来匹配文档,每个文档命中值就会有一个score值,可以按照score值进行排序

②按照指定字段的值可以倒序,也可以正序③按照指定地理位置的距离来进行排序,这里需要注意的就是在排序字段上如果是text类型就必须开启 fielddata,而keyword可以直接用来排序,所以建议如果要给字段排序就最好声明为keyword类型。

GET dc-elk-bill-info-end-2021.06/_search
{
  "sort": [
    {
      "logTime": {
        "order": "desc"
      }
    }
  ]
}


2.3.5 经纬度查询(GeoDistanceQueryBuilder)
//以某点为中心,搜索指定范围
nativeSearchQuery.withQuery(QueryBuilders.boolQuery().filter(new GeoDistanceQueryBuilder("location").point(40.010955, 118.68545).distance(1, DistanceUnit.KILOMETERS)));
// 按距离升序
nativeSearchQuery.withSort(new GeoDistanceSortBuilder("location",40.010955, 118.68545).unit(DistanceUnit.KILOMETERS).order(SortOrder.ASC));
  • 说明:

GeoDistanceQueryBuilder : 地理位置查询对象

point :中心点坐标

distance:距离 单位/km

location:坐标点 圆心所在位置

geo_distance :找出与指定位置在给定距离内的点

geo_polygon : 找出落在多边形中的点

geo_bounding_box : 找出落在指定矩形框中的坐标点

geo_distance_range :找出与指定点距离在给定最小距离和最大距离之间的点



2.4 聚合查询

  • Aggregation -> SubAggregation

SubAggregation是在原来的Aggregation的计算结果中进一步做聚合计算

Elasticsearch 默认对于分词的字段(text类型)不支持聚合

  • 返回数据字段:

name: 和请求中的Aggregation的名字对应

buckets: 每个Bucket对应Agggregation结果中每一个可能的取值和相应的聚合结果.

Bucket 属性:

key: 对应的是聚合维度可能的取值, 具体的值和Aggregation的类型有关, 比如Term aggregation (按交易类型计算总金额), 那么Bucket key值就是所有可能的交易类型 (credit/debit etc). 又比如DateHistogram aggregation (按天计算交易笔数), 那么Bucket key值就是具体的日期.

docCount: 对应的是每个桶中的文本数量.

value: 对应的是聚合指标的计算结果. 注意如果是多层Aggregation计算, 中间层的Aggregation value一般没有值, 比如Term aggregation. 只有到底层具体计算指标的Aggregation才有值.

aggregations: 对应请求中当前Aggregation的subAggregation的计算结果 (如果存在)

  • 常用的SQL运算符和聚合函数对应的ES Builder:
释义 Sql element Aggregation Type Code to build
统计数量 count(field) ValueCountAggregationBuilder AggregationBuilders.count(metricsName).field(fieldName)
去重再统计数量 count(distinct field) CardinalityAggregationBuilder AggregationBuilders.cardinality(metricsName).field(fieldName)
求和 sum(field) SumAggregationBuilder AggregationBuilders.sum(metricsName).field(fieldName)
最小值 min(field) MinAggregationBuilder AggregationBuilders.min(metricsName).field(fieldName)
最大值 max(field) MaxAggregationBuilder AggregationBuilders.max(metricsName).field(fieldName)
平均值 avg(field) AvgAggregationBuilder AggregationBuilders.avg(metricsName).field(fieldName)


2.4.1 获取平均值(AvgAggregationBuilder)
//获取平均值
AvgAggregationBuilder avg = AggregationBuilders.avg("avg_of_field").field("resultTime");
builder.addAggregation(avg);
GET dc-elk-bill-info-end-2021.06/_search
{
  "aggs": {
    "terms_by_field": {
      "avg": {
        "field": "resultTime"
      }
    }
  }
}



2.5 复杂查询



2.5.1 聚合后求值及统计数据(AggregationBuilders.sum)
//根据region字段聚合后totalAmount的总值及payNameCode的个数
TermsAggregationBuilder terms = AggregationBuilders.terms("terms_by_field")
        .field("region.keyword")
        .minDocCount(1)
        .size(20)
        .subAggregation(AggregationBuilders
                .sum("sum_of_field")
                .field("totalAmount"))
        .subAggregation(AggregationBuilders
                .cardinality("count_field_distinct")
                .field("payNameCode.keyword"));
nativeSearchQuery.addAggregation(terms);
  • TermsAggregationBuilder : 聚合查询对象
  • AggregationBuilders.terms : 相当于sql中的group by
  • terms_by_field : terms聚合函数结果名
  • terms(“”).field : 匹配对应的字段名
  • minDocCount : 返回最小的文档数。强制返回空数据。如果是0,时间间隔内缺少数据,则自动补充0.一般场景就是返回空数据,减少程序的处理。(通过设置min_doc_count和shard_min_doc_count来规定最小的文档数目,只有满足这个参数要求的个数的词条才会被记录返回)
  • size : 返回的条数设置
  • subAggregation : 添加子聚合
  • sum_of_field : sum聚合函数结果名
  • AggregationBuilders.cardinality : 类似sql中 count(distinct),先去重再求和
  • count_field_distinct : cardinality函数结果名
GET dc-elk-bill-info-end-2021.06/_search
{
  "aggs": {
    "terms_by_field": {
      "terms": {
        "field": "region.keyword",
        "size": 20,
        "min_doc_count": 1
      },
      "aggregations": {
        "sum_of_field": {
          "sum": {
            "field": "totalAmount"
          }
        },
        "count_field_distinct": {
          "cardinality": {
            "field": "payNameCode.keyword"
          }
        }
      }
    }
  }
}


2.5.2 每分钟各个执收单位的开票数据(dateHistogram)
// 每分钟各个执收单位的开票数据
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
DateHistogramAggregationBuilder dateHistogram = AggregationBuilders
        .dateHistogram("count_by_time")
        .field("@timestamp")
        .fixedInterval(DateHistogramInterval.minutes(1))
        .format("yyyy-MM-dd HH:mm:ss")
        .order(BucketOrder.key(false))
        .minDocCount(0)
        .extendedBounds(new ExtendedBounds(formatter.format("2021-06-25 00:00:00"), formatter.format("2021-06-25 23:59:59")))
        .subAggregation(AggregationBuilders.terms("terms_by_field")
                .field("instance_id.keyword")
                .minDocCount(1)
                .size(200));
  • 说明:

date_histgram 比普通的 histogram 支持更多的时间特性,可以灵活选择时间单位和支持 date math,比如时区的灵活变化

  • AggregationBuilders.dateHistogram(“”) : 按照时间来构建集合(桶)Buckts的,当我们需要按照时间进行做一些数据统计的时候,就可以使用它来进行时间维度上构建指标分析,基于文档中的某个【日期类型】字段,,以【日期间隔】来桶分聚合。
  • count_by_time : dateHistogram函数名称
  • fixedInterval :指定间隔(又称桶大小)来匹配数据
  • DateHistogramInterval.minutes() : 声明时间范围
  • format : 规范返回时间格式
  • order : 排序
  • BucketOrder.key():按key的升序或降序排序
  • minDocCount :返回最小的文档数。强制返回空数据。如果是0,时间间隔内缺少数据,则自动补充0.一般场景就是返回空数据,减少程序的处理。(通过设置min_doc_count和shard_min_doc_count来规定最小的文档数目,只有满足这个参数要求的个数的词条才会被记录返回)
  • extendedBounds:此值只有当min_doc_count 为0时才具有意义。此值与min_doc_count 一起使用,是强制返回空数据。
GET dc-elk-bill-info-end-2021.06/_search
{
  "aggs": {
    "count_by_time": {
      "date_histogram": {
        "field": "@timestamp",
        "interval": "minute",
        "format": "yyyy-MM-dd HH:mm:ss",
        "order": {
          "_key": "asc"
        },
        "min_doc_count": 0,
        "extended_bounds": {
          "min": "2021-06-25 00:00:00",
          "max": "2021-06-25 23:59:59"
        }
      },
      "aggs": {
        "terms_by_field": {
          "terms": {
            "field": "instance_id.keyword",
            "size": 200,
            "min_doc_count": 1
          }
        }
      }
    }
  }
}


2.5.3 统计不同票据类别各个时间段的数据(AggregationBuilders.range)
// 统计不同票据类别各个时间段的数据
TermsAggregationBuilder termsAggregationBuilder = AggregationBuilders.terms("terms_by_field")
        .field("invAttribute.keyword")
        .minDocCount(0)
        .subAggregation(AggregationBuilders.range("count_field_range")
                .field("resultTime")
                .addUnboundedTo(30)
                .addRange(30, 60)
                .addRange(60, 180)
                .addUnboundedFrom(180));
  • addUnboundedTo(30) :统计大于三十的数据
  • addRange(30, 60) : 统计大于等于三十并且小于60的数据
  • addUnboundedFrom(180):统计大于180的数据
GET dc-elk-bill-info-end-2021.06/_search
{
  "aggs": {
    "terms_by_field": {
      "terms": {
        "field": "invAttribute.keyword",
        "size": 10,
        "min_doc_count": 10
      },
      "aggs": {
        "count_field_range": {
          "range": {
            "field": "resultTime",
            "ranges": [
              {
                "to": 30
              },
              {
                "from": 30,
                "to": 60
              },
              {
                "from": 60,
                "to": 180
              },
              {
                "from": 180
              }
            ]
          }
        }
      }
    }
  }
}
  • 查询条件构造完成
elasticsearchRestTemplate.search(query,  EsBillInfoEnd.class);
  • 或对象中未声明索引名称,代码中声明:
elasticsearchRestTemplate.search(query,  EsBillInfoEnd.class, IndexCoordinates.of(indexName));



3.查询完成返回数据处理

SearchHits为es查询返回统一封装对象,如果要获取返回对象则需根据定义的函数名获取出对象数据

  • 示例,如果返回数据为:
{
  "took": 10,
  "timed_out": false,
  "_shards": {
    "total": 1,
    "successful": 1,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": {
      "value": 10000,
      "relation": "gte"
    },
    "max_score": null,
    "hits": [
      
    ]
  },
  "aggregations": {
    "terms_by_field": {
      "doc_count_error_upper_bound": 0,
      "sum_other_doc_count": 0,
      "buckets": [
        {
          "key": "330601",
          "doc_count": 53337,
          "count_field_range": {
            "buckets": [
              {
                "key": "*-30.0",
                "to": 30.0,
                "doc_count": 15332
              },
              {
                "key": "30.0-60.0",
                "from": 30.0,
                "to": 60.0,
                "doc_count": 25883
              },
              {
                "key": "60.0-180.0",
                "from": 60.0,
                "to": 180.0,
                "doc_count": 11952
              },
              {
                "key": "180.0-*",
                "from": 180.0,
                "doc_count": 170
              }
            ]
          }
        }
      ]
    }
  }
}

则循环返回数据代码为:

SearchHits<EsBillInfoEnd> articleEntities = elasticsearchRestTemplate.search(nativeSearchQuery.build(), EsBillInfoEnd.class);
//terms_by_field : 查询时定义的函数名
ParsedTerms terms = articleEntities.getAggregations().get("terms_by_field");
for (Terms.Bucket bucket : terms.getBuckets()) {
    String billType = bucket.getKeyAsString();
    //二层函数则再进行循环
}



版权声明:本文为m0_37729339原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。