Springboot项目中使用Elasticsearch的RestClient

上一篇介绍了Elasticsearch的入门《5000字详说Elasticsearch入门(一)》,本篇介绍Springboot如何集成使用Elasticsearch。分为3步:配置properties文件、引入pom依赖、配置RestHighLevelClient类。

1、选择ES的Client API

我们知道Elasticsearch是一款Restful API风格的分布式搜索引擎。ES Client有两种连接方式:TransportClient 和 RestClient。TransportClient通过TCP方式访问ES,RestClient方式通过Http方式访问ES。ES在7.0中已经弃用TransportClient,在8.0中完全删除它,所以建议使用RestClient的方式。

RestClient方式有多种实现,比如:ES自带的RestHighLevelClient 、Springboot实现的ElasticsearchRestTemplate 。笔者建议使用RestHighLevelClient,原因有3个:

  • 使用RestHighLevelClient比较灵活,可以直接使用ES的DSL语法,实现复杂查询,同时没有与其他部件绑定,所以版本可以自由选择。
  • 由于ElasticsearchRestTemplatespring-boot-starter-data-elasticsearch封装的工具类,虽然使用上稍微方便一些,但是失去了灵活性,出现问题时也不易排查。而且ElasticsearchRestTemplate本身与spring-boot-starter-data-elasticsearch紧密依赖。如果想升级ElasticsearchRestTemplate,那就必须连带升级项目的Springboot版本,这个风险就比较高了,一般项目的Springboot版本不会轻易升级。
  • 还有些第三方的插件,将SQL转成ES的DSL,笔者也不建议使用。它们即不灵活,更新速度也慢。这里也再次吐槽下,ES的DSL设计确实有点反人类,不过也不用死记,可以通过归类记忆核心内容,其余使用细节直接查阅官方文档就好。

整理来说,用ES提供的RestHighLevelClient是最稳定最方便的。由于使用RestHighLevelClient,多少会涉及到ES的DSL语法,详见官网:https://www.elastic.co/guide/en/elasticsearch/reference/7.17/query-dsl-match-query-phrase.html

下文中的示例中使用RestHighLevelClient时,也会附带上对应的DSL语法。

2、配置properties文件

# ES集群机器
elasticsearch.hosts=10.20.1.29,10.20.0.91,10.20.0.93
# ES提供服务的端口号
elasticsearch.port=9200

3、引入pom依赖

<dependency>
    <groupId>org.elasticsearch.client</groupId>
    <artifactId>elasticsearch-rest-high-level-client</artifactId>
    <version>7.10.2</version>
</dependency>
<dependency>
    <groupId>org.elasticsearch.client</groupId>
    <artifactId>elasticsearch-rest-client</artifactId>
    <version>7.10.2</version>
</dependency>
<dependency>
    <groupId>org.elasticsearch</groupId>
    <artifactId>elasticsearch</artifactId>
    <version>7.10.2</version>
</dependency>

4、配置RestHighLevelClient

@Data
@Configuration
@ConfigurationProperties(prefix = "elasticsearch")
public class ElasticSearchConfig {

    private String hosts;
    private Integer port;

    @Bean
    public RestHighLevelClient restHighLevelClient() {
        HttpHost[] httpHosts = Arrays.stream(hosts.split(","))
                .filter(e -> !StringUtils.isEmpty(e))
                .map(e -> new HttpHost(e, port, "http"))
                .toArray(HttpHost[]::new);

        return new RestHighLevelClient(
                RestClient.builder(
                        httpHosts
                )
        );
    }
}

至此Springboot与Elasticsearch的集成已经结束,接下来就是使用了。

5、使用RestClient API

下文演示常规场景下的RestClient API的使用方式和对应的DSL语法,涉及到的相关完整代码见如下地址:

5.1、创建索引,指定Mapping

通过代码里调用RestClient的API也可以创建索引,但是笔者建议统一通过如下方式建立,字段和类型非常清晰可控,方便统一管理。比如创建一个goods索引:

PUT /goods
{
  "mappings": {
    "properties": {
      "brandName": {
        "type": "keyword"
      },
      "categoryName": {
        "type": "keyword"
      },
      "createTime": {
        "type": "date",
        "format": "yyyy-MM-dd HH:mm:ss"
      },
      "id": {
        "type": "keyword"
      },
      "price": {
        "type": "double"
      },
      "saleNum": {
        "type": "integer"
      },
      "status": {
        "type": "integer"
      },
      "stock": {
        "type": "integer"
      },
      "title": {
        "type": "text",
        "analyzer": "ik_max_word",
        "search_analyzer": "ik_smart"
      }
    }
  }
}

5.2、创建实例类

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Goods {

    /**
     * 商品编号
     */
    private Long id;

    /**
     * 商品标题
     */
    private String title;

    /**
     * 商品价格
     */
    private BigDecimal price;

    /**
     * 商品库存
     */
    private Integer stock;

    /**
     * 商品销售数量
     */
    private Integer saleNum;

    /**
     * 商品分类
     */
    private String categoryName;

    /**
     * 商品品牌
     */
    private String brandName;

    /**
     * 上下架状态
     */
    private Integer status;

    /**
     * 商品创建时间
     */
    @JSONField(format = "yyyy-MM-dd HH:mm:ss")
    private Date createTime;
}

5.3、使用API操作数据

下面介绍主流场景下API的使用,每个场景会配上对应的DSL语法。更多语法详见官方文档:https://www.elastic.co/guide/en/elasticsearch/reference/7.17/query-dsl-match-query-phrase.html

5.3.1、增加文档

// 创建索引请求对象
IndexRequest indexRequest = new IndexRequest("goods").id(goods.getId() + "").source(data, XContentType.JSON);
// 执行增加文档
IndexResponse response = restHighLevelClient.index(indexRequest, RequestOptions.DEFAULT);

DSL:

PUT goods/_doc/3
{
  "id": 3,
  "brandName": "华为",
  "categoryName": "手机",
  "createTime": "2023-10-23 19:12:56",
  "price": 5999,
  "saleNum": 1001,
  "status": 1,
  "stock": 900,
  "title": "华为自研芯片Meta60测试机"
}

5.3.2、更新文档

// 创建索引请求对象
UpdateRequest updateRequest = new UpdateRequest("goods", "4");
// 设置更新文档内容
updateRequest.doc(data, XContentType.JSON);
// 执行更新文档
UpdateResponse response = restHighLevelClient.update(updateRequest, RequestOptions.DEFAULT);

DSL:

POST goods/_update/3
{
  "doc": {
    "title":"华为自研芯片Meta60测试机111"
  }
}

5.3.3、删除文档

// 创建删除请求对象
DeleteRequest deleteRequest = new DeleteRequest("goods", "3");
// 执行删除文档
DeleteResponse response = restHighLevelClient.delete(deleteRequest, RequestOptions.DEFAULT);

DSL:

DELETE goods/_doc/3

5.3.4、Match匹配查询

// 构建查询条件 (查询全部)
MatchAllQueryBuilder matchAllQueryBuilder = QueryBuilders.matchAllQuery();
// 创建查询源构造器
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchSourceBuilder.query(matchAllQueryBuilder);
// 设置分页
searchSourceBuilder.from(0); // 从第几条开始,不包含它
searchSourceBuilder.size(3); // 要取多少条数据
// 设置排序
searchSourceBuilder.sort("price", SortOrder.ASC);
// 设置源字段过虑,第一个参数结果集包括哪些字段,第二个参数表示结果集不包括哪些字段;查询的文档只包含哪些指定的字段
searchSourceBuilder.fetchSource(new String[]{"id", "title", "categoryName"}, new String[]{});
// 创建查询请求对象,将查询对象配置到其中
SearchRequest searchRequest = new SearchRequest("goods");
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 转换成对象
        Goods goods = JSON.parseObject(hit.getSourceAsString(), Goods.class);
        // 输出查询信息
        log.info(goods.toString());
    }
}

DSL:

POST goods/_search
{
  "query": {
    "match_all": {
    
    }
  }
}

POST goods/_search
{
  "query": {
    "match": {
      "title": "移动多余"
    }
  }
}

5.3.5、MatchPhrase查询

短语匹配会把查询文本看做短语,有顺序要求。

// 构建查询条件
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
// title是 舒适轻巧 时,可以查到,改成 轻巧舒适 时,则查不到,因为短语匹配,会把查询文本看做短语,有顺序要求
searchSourceBuilder.query(QueryBuilders.matchPhraseQuery("title", "轻巧舒适"));
// 创建查询请求对象,将查询对象配置到其中
SearchRequest searchRequest = new SearchRequest("goods");
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 转换成对象
        Goods goods = JSON.parseObject(hit.getSourceAsString(), Goods.class);
        // 输出查询信息
        log.info(goods.toString());
    }
}

DSL:

POST goods/_search
{
  "query": {
    "match_phrase": {
      "title": "轻巧舒适"
    }
  }
}

5.3.6、Term查询

Term查询时不对查询文本做分词。

// 构建查询条件(注意:termQuery 支持多种格式查询,如 boolean、int、double、string 等,这里使用的是 string 的查询)
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
// 表示检索title字段值为 李宁的跑鞋 的文档,如果是match则可以查到,term查询不到,因为term查询时不对查询文本做分词
searchSourceBuilder.query(QueryBuilders.termQuery("title", "李宁的跑鞋"));
// 创建查询请求对象,将查询对象配置到其中
SearchRequest searchRequest = new SearchRequest("goods");
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 转换成对象
        Goods goods = JSON.parseObject(hit.getSourceAsString(), Goods.class);
        // 输出查询信息
        log.info("=======" + goods.toString());
    }
}

DSL:

POST goods/_search
{
  "query": {
    "term": {
      "title": {
        "value": "李宁的跑鞋"
      }
    }
  }
}

5.3.7、设置排序

// 设置排序
searchSourceBuilder.sort("price", SortOrder.ASC);

DSL:

POST goods/_search
{
  "query": {
    "match_all": {
    
    }
  },
  "from": 6,
  "size": 2,
  "sort": [
    {
      "price": {
        "order": "asc"
      }
    }
  ]
}

5.3.8、通配符查询

// 构建查询条件
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
// 查询所有以 “鞋” 结尾的商品信息
searchSourceBuilder.query(QueryBuilders.wildcardQuery("title", "*鞋"));
// 创建查询请求对象,将查询对象配置到其中
SearchRequest searchRequest = new SearchRequest("goods");
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 转换成对象
        Goods goods = JSON.parseObject(hit.getSourceAsString(), Goods.class);
        // 输出查询信息
        log.info(goods.toString());
    }
}

DSL:

POST goods/_search
{
  "query": {
    "wildcard": {
      "title": {
        "value": "*鞋"
      }
    }
  }
}

5.3.9、范围查询

// 构建查询条件
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchSourceBuilder.query(QueryBuilders.rangeQuery("price").gte(1000));

DSL:

POST goods/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "title": "华为"
          }
        }
      ],
      "filter": [
        {
          "range": {
            "price": {
              "gte": 5000,
              "lte": 10000
            }
          }
        }
      ]
    }
  }
}

5.3.10、Scroll滚动查询

滚动查询可以用来解决深度分页查询问题,每次都要记住上一次的scrollId。

// 假设用户想获取第5页数据,其中每页1条
int pageNo = 3;
int pageSize = 1;

// 定义请求对象
SearchRequest searchRequest = new SearchRequest("goods");

// 构建查询条件
SearchSourceBuilder builder = new SearchSourceBuilder();
searchRequest.source(builder.query(QueryBuilders.matchAllQuery()).sort("price", SortOrder.DESC).size(pageSize));
String scrollId = null;
// 3、发送请求到ES
SearchResponse scrollResponse = null;
// 设置游标id存活时间
Scroll scroll = new Scroll(TimeValue.timeValueMinutes(2));
// 记录所有游标id
List<String> scrollIds = new ArrayList<>();
for (int i = 0; i < pageNo; i++) {
    try {
        // 首次检索
        if (i == 0) {
            //记录游标id
            searchRequest.scroll(scroll);
            // 首次查询需要指定索引名称和查询条件
            SearchResponse response = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
            // 下一次搜索要用到该游标id
            scrollId = response.getScrollId();
        }
        // 非首次检索
        else {
            // 不需要在使用其他条件,也不需要指定索引名称,只需要使用执行游标id存活时间和上次游标id即可,毕竟信息都在上次游标id里面呢
            SearchScrollRequest searchScrollRequest = new SearchScrollRequest(scrollId);
            searchScrollRequest.scroll(scroll);
            scrollResponse = restHighLevelClient.scroll(searchScrollRequest, RequestOptions.DEFAULT);
            // 下一次搜索要用到该游标id
            scrollId = scrollResponse.getScrollId();
        }
        // 记录所有游标id
        scrollIds.add(scrollId);
    } catch (Exception e) {
        e.printStackTrace();
    }
}

// 查询完毕,清除游标id
ClearScrollRequest clearScrollRequest = new ClearScrollRequest();
clearScrollRequest.scrollIds(scrollIds);
try {
    restHighLevelClient.clearScroll(clearScrollRequest, RequestOptions.DEFAULT);
} catch (IOException e) {
    System.out.println("清除滚动查询游标id失败");
    e.printStackTrace();
}

// 4、处理响应结果
System.out.println("滚动查询返回数据:");
assert scrollResponse != null;
SearchHits hits = scrollResponse.getHits();
for (SearchHit hit : hits) {
    // 将 JSON 转换成对象
    Goods goods = JSON.parseObject(hit.getSourceAsString(), Goods.class);
    // 输出查询信息
    log.info(goods.toString());
}

DSL:

# 第一次使用 scroll API
POST goods/_search?scroll=2m
{
  "query": {
    "match_all": {}
  },
  "size": 2
}
# 进行翻页
POST /_search/scroll                                                    
{
  "scroll" : "2m",   
  "scroll_id" : "FGluY2x1ZGVfY29udGV4dF91dWlkDXF1ZXJ5QW5kRmV0Y2gBFkxBWkYwOGw2U1dPSF94aHZTelFkaWcAAAAAAAADHhZoU05ERFl3WFIycXM3M3JKMmRQVkJB" 
}

5.3.11、组合查询

组合查询一般用到bool

// 创建 Bool 查询构建器
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
// 构建查询条件
boolQueryBuilder
        .must(QueryBuilders.matchQuery("title", "李宁"))
        .filter()
        .add(QueryBuilders.rangeQuery("createTime").format("yyyyMMdd")
                .gte("20231023").lte("20231025"));
// 构建查询源构建器
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchSourceBuilder.query(boolQueryBuilder);
searchSourceBuilder.size(100);
// 创建查询请求对象,将查询对象配置到其中
SearchRequest searchRequest = new SearchRequest("goods");
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 转换成对象
        Goods goods = JSON.parseObject(hit.getSourceAsString(), Goods.class);
        // 输出查询信息
        log.info(goods.toString());
    }
}

DSL:

POST goods/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "title": "李宁"
          }
        }
      ],
      "filter": [
        {
          "range": {
            "price": {
              "gte": 5000,
              "lte": 10000
            }
          }
        }
      ]
    }
  }
}

5.3.12、高亮查询

//查询条件(词条查询:对应ES query里的match)
MatchQueryBuilder matchQueryBuilder = QueryBuilders.matchQuery("title", "华为手机");

//设置高亮三要素
// field: 你的高亮字段
// preTags :前缀
// postTags:后缀
HighlightBuilder highlightBuilder = new HighlightBuilder()
        .field("title")
        .preTags("<font color='blue'>")
        .postTags("</font>");

// 构建查询源构建器
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchSourceBuilder.query(matchQueryBuilder);
searchSourceBuilder.highlighter(highlightBuilder);
searchSourceBuilder.size(100);
// 创建查询请求对象,将查询对象配置到其中
SearchRequest searchRequest = new SearchRequest("goods");
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 转换成对象
        Goods goods = JSON.parseObject(hit.getSourceAsString(), Goods.class);

        // 获取高亮的数据
        HighlightField highlightField = hit.getHighlightFields().get("title");
        System.out.println("高亮名称:" + highlightField.getFragments()[0].string());

        // 替换掉原来的数据
        Text[] fragments = highlightField.getFragments();
        if (fragments != null && fragments.length > 0) {
            StringBuilder title = new StringBuilder();
            for (Text fragment : fragments) {
                //System.out.println(fragment);
                title.append(fragment);
            }
            goods.setTitle(title.toString());
        }
        // 输出查询信息
        log.info(goods.toString());
    }
}

DSL:

POST goods/_search
{
  "query": {
    "match": {
      "title": "跑鞋"
    }
  },
  "highlight": {
    "fields": {
      "body": {
        "pre_tags": [
          "<font color='red'>"
        ],
        "post_tags": [
          "</font>"
        ]
      },
      "title": {}
    }
  }
}

5.3.13、聚合查询

// 构建查询条件
MatchAllQueryBuilder matchAllQueryBuilder = QueryBuilders.matchAllQuery();
// 创建查询源构造器
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchSourceBuilder.query(matchAllQueryBuilder);

// 获取最贵的商品
AggregationBuilder maxPrice = AggregationBuilders.max("maxPrice").field("price");
searchSourceBuilder.aggregation(maxPrice);
// 获取最便宜的商品
AggregationBuilder minPrice = AggregationBuilders.min("minPrice").field("price");
searchSourceBuilder.aggregation(minPrice);

// 创建查询请求对象,将查询对象配置到其中
SearchRequest searchRequest = new SearchRequest("goods");
searchRequest.source(searchSourceBuilder);
// 执行查询,然后处理响应结果
SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
Aggregations aggregations = searchResponse.getAggregations();
ParsedMax max = aggregations.get("maxPrice");
log.info("最贵的价格:" + max.getValue());
ParsedMin min = aggregations.get("minPrice");
log.info("最便宜的价格:" + min.getValue());

DSL:

POST goods/_search
{
  "aggs": {
    "max_price": {
      "max": {
        "field": "price"
      }
    }
  }
}

5.3.14、分组查询

// 构建查询条件
MatchAllQueryBuilder matchAllQueryBuilder = QueryBuilders.matchAllQuery();
// 创建查询源构造器
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchSourceBuilder.query(matchAllQueryBuilder);

// 根据商品分类进行分组查询
TermsAggregationBuilder aggBrandName = AggregationBuilders
        .terms("brandNameName")
        .field("brandName");
searchSourceBuilder.aggregation(aggBrandName);

// 创建查询请求对象,将查询对象配置到其中
SearchRequest searchRequest = new SearchRequest("goods");
searchRequest.source(searchSourceBuilder);
// 执行查询,然后处理响应结果
SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
Aggregations aggregations = searchResponse.getAggregations();
ParsedStringTerms aggBrandName1 = aggregations.get("brandNameName");
for (Terms.Bucket bucket : aggBrandName1.getBuckets()) {
    // 分组名 ==== 数量
    System.out.println(bucket.getKeyAsString() + "====" + bucket.getDocCount());
}

DSL:

POST goods/_search
{
  "aggs": {
    "brandNameName": {
      "terms": {
        "field": "brandName"
      }
    }
  }
}

5.3.15、子查询

// 构建查询条件
MatchAllQueryBuilder matchAllQueryBuilder = QueryBuilders.matchAllQuery();
// 创建查询源构造器
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchSourceBuilder.query(matchAllQueryBuilder);

// 根据商品分类进行分组查询,并且获取分类商品中的平均价格
TermsAggregationBuilder subAggregation = AggregationBuilders
        .terms("brandNameName")
        .field("brandName")
        .subAggregation(AggregationBuilders.avg("avgPrice").field("price"));
searchSourceBuilder.aggregation(subAggregation);

// 创建查询请求对象,将查询对象配置到其中
SearchRequest searchRequest = new SearchRequest("goods");
searchRequest.source(searchSourceBuilder);
// 执行查询,然后处理响应结果
SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
Aggregations aggregations = searchResponse.getAggregations();
ParsedStringTerms aggBrandName1 = aggregations.get("brandNameName");
for (Terms.Bucket bucket : aggBrandName1.getBuckets()) {
    // 获取聚合后的品牌的平均价格,注意返回值不是Aggregation对象,而是指定的ParsedAvg对象
    ParsedAvg avgPrice = bucket.getAggregations().get("avgPrice");
    // 分组名 ==== 平均价格
    System.out.println(bucket.getKeyAsString() + "====" + avgPrice.getValueAsString());
}

DSL:

POST goods/_search
{
  "aggs": {
    "brandNameName": {
      "terms": {
        "field": "brandName"
      },
      "aggs": {
        "avgPrice": {
          "avg": {
            "field": "price"
          }
        }
      }
    }
  }
}

5.3.16、子查询下的子查询

子查询下的子查询有点绕。

// 构建查询条件
MatchAllQueryBuilder matchAllQueryBuilder = QueryBuilders.matchAllQuery();
// 创建查询源构造器
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchSourceBuilder.query(matchAllQueryBuilder);

// 注意这里聚合写的位置不要写错,很容易搞混,错一个括号就不对了
TermsAggregationBuilder subAggregation = AggregationBuilders
        .terms("categoryNameAgg").field("categoryName")
        .subAggregation(AggregationBuilders.avg("categoryNameAvgPrice").field("price"))
        .subAggregation(AggregationBuilders.terms("brandNameAgg").field("brandName")
                .subAggregation(AggregationBuilders.avg("brandNameAvgPrice").field("price")));
searchSourceBuilder.aggregation(subAggregation);

// 创建查询请求对象,将查询对象配置到其中
SearchRequest searchRequest = new SearchRequest("goods");
searchRequest.source(searchSourceBuilder);
// 执行查询,然后处理响应结果
SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
//获取总记录数
System.out.println("totalHits = " + searchResponse.getHits().getTotalHits().value);
// 获取聚合信息
Aggregations aggregations = searchResponse.getAggregations();
ParsedStringTerms categoryNameAgg = aggregations.get("categoryNameAgg");
//获取值返回
for (Terms.Bucket bucket : categoryNameAgg.getBuckets()) {
    // 获取聚合后的分类名称
    String categoryName = bucket.getKeyAsString();
    // 获取聚合命中的文档数量
    long docCount = bucket.getDocCount();
    // 获取聚合后的分类的平均价格,注意返回值不是Aggregation对象,而是指定的ParsedAvg对象
    ParsedAvg avgPrice = bucket.getAggregations().get("categoryNameAvgPrice");

    System.out.println(categoryName + "======平均价:" + avgPrice.getValue() + "======数量:" + docCount);

    ParsedStringTerms brandNameAgg = bucket.getAggregations().get("brandNameAgg");
    for (Terms.Bucket brandeNameAggBucket : brandNameAgg.getBuckets()) {
        // 获取聚合后的品牌名称
        String brandName = brandeNameAggBucket.getKeyAsString();

        // 获取聚合后的品牌的平均价格,注意返回值不是Aggregation对象,而是指定的ParsedAvg对象
        ParsedAvg brandNameAvgPrice = brandeNameAggBucket.getAggregations().get("brandNameAvgPrice");

        System.out.println("     " + brandName + "======" + brandNameAvgPrice.getValue());
    }
}

DSL:

POST goods/_search
{
  "aggs": {
    "categoryNameName": {
      "terms": {
        "field": "categoryName"
      },
      "aggs": {
        "avgPrice": {
          "avg": {
            "field": "price"
          }
        },
        "brandNameAgg": {
          "terms": {
            "field": "brandName"
          },
          "aggs": {
            "brandNameAvg": {
              "avg": {
                "field": "price"
              }
            }
          }
        }
      }
    }
  }
}

RestClient API使用的参考文献:https://huaweicloud.csdn.net/637f7baddacf622b8df85bf5.html

6、总结

本文主要介绍了Springboot与Elasticsearch集成和使用,重点内容如下:

  • 选择RestHighLevelClient作为ES的RestClient。
  • 核心步骤:增加properties配置、引入pom、配置conf类。
  • 调用API、使用DSL语法。

本篇完结!感谢你的阅读,欢迎点赞 关注 收藏 私信!!!

原文链接:http://www.mangod.top/articles/2023/10/25/1698206210944.htmlhttps://mp.weixin.qq.com/s/5SMObCipKYdCCsHa0FSVUg

posted @ 2024-02-11 11:08  程序员半支烟  阅读(192)  评论(0编辑  收藏  举报