Elasticsearch 入门到实战:安装 + CRUD + 查询

一、Elasticsearch 是什么?​

Elasticsearch(简称 ES) 是基于 Lucene 的分布式全文搜索引擎,支持近实时搜索、海量数据存储、高可用集群、RESTful API,广泛用于日志分析、商品搜索、数据可视化等场景。​


在搜索时为什么我们要用 Elasticsearch,而不是 MySQL 模糊匹配?

在实际业务中,商品搜索、日志检索、全文搜索 这些场景,MySQL 根本扛不住。

1. MySQL 的 like '%关键词%' 为什么慢?

  • 无法走索引,必须全表扫描
  • 数据量越大,速度越慢
  • 不支持分词,不支持相关性打分
  • 不支持高亮、同义词、拼音搜索

简单说:数据量一大,like 查询直接拖垮数据库。


2. Elasticsearch 为什么快?------ 倒排索引原理

ES 快的核心原因只有一个:它用了 倒排索引(Inverted Index)。

我用最简单的方式讲清楚:

正常数据库(正排索引)

文档1 → 我喜欢华为手机

文档2 → 华为手机信号好

文档3 → 苹果手机也不错

倒排索引(关键词 → 文档)

华为 → 文档1、文档2

手机 → 文档1、文档2、文档3

苹果 → 文档3 喜欢 → 文档1

当你搜索:"华为"

ES 不需要扫全表,直接查倒排索引:华为 → 文档 1、文档 2 直接返回,毫秒级响应。


3. 倒排索引三大优势

  1. 检索极快:不需要全表扫描
  2. 支持分词:把一句话切成词语
  3. 相关性打分:最匹配的排前面

MySQL 适合存数据,Elasticsearch 适合搜数据。

MySQL 用 B+ 树,适合精确查询。

Elasticsearch 用倒排索引,适合全文检索。


Elasticsearch核心概念​

  • 索引(Index):对应 MySQL 的数据库
  • 文档(Document):对应 MySQL 的行数据
  • 映射(Mapping):对应 MySQL 的表结构
  • 分片 / 副本:保证分布式存储与高可用

文档数据会被序列化为json格式后存储在elasticsearch

我们统一的把mysql与elasticsearch的概念做一下对比


Kibana是elastic公司提供的用于操作Elasticsearch的可视化控制台。它的功能非常强大,包括:

  • 对Elasticsearch数据的搜索、展示

  • 对Elasticsearch数据的统计、聚合,并形成图形化报表、图形

  • 对Elasticsearch的集群状态监控

  • 它还提供了一个开发控制台(DevTools),在其中对Elasticsearch的Restful的API接口提供了语法提示


elasticsearch安装

通过下面的Docker命令即可安装单机版本的elasticsearch:

XML 复制代码
docker run -d \
  --name es \
  -e "ES_JAVA_OPTS=-Xms512m -Xmx512m" \
  -e "discovery.type=single-node" \
  -v es-data:/usr/share/elasticsearch/data \
  -v es-plugins:/usr/share/elasticsearch/plugins \
  --privileged \
  --network hm-net \
  -p 9200:9200 \
  -p 9300:9300 \
  elasticsearch:7.12.1

访问9200端口,即可看到响应的Elasticsearch服务的基本信息:


安装kibana

XML 复制代码
docker run -d \
--name kibana \
-e ELASTICSEARCH_HOSTS=http://es:9200 \
--network=hm-net \
-p 5601:5601  \
kibana:7.12.1

访问5601端口,即可看到控制台页面:


ik分词器

Elasticsearch的关键就是倒排索引,而倒排索引依赖于对文档内容的分词,而分词则需要高效、精准的分词算法,IK分词器就是这样一个中文分词算法。

安装:

XML 复制代码
docker exec -it es ./bin/elasticsearch-plugin  install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.12.1/elasticsearch-analysis-ik-7.12.1.zip

IK分词器包含两种模式:

  • ik_smart:智能语义切分

  • ik_max_word:最细粒度切分

词典:随着互联网的发展出现了很多新的词语,在原有的词汇列表中并不存在,IK分词器无法对这些词汇分词,要想正确分词,IK分词器的词库也需要不断的更新,IK分词器提供了扩展词汇的功能

在分词器的配置目录下

XML 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
        <comment>IK Analyzer 扩展配置</comment>
        <!--用户可以在这里配置自己的扩展字典 *** 添加扩展词典-->
        <entry key="ext_dict">ext.dic</entry>
</properties>

改目录下目录新建一个 ext.dic,然后在里面添加新的词汇,重启后ik分词器就能正确识别ext.dic的这些单词


CRUD操作:

由于Elasticsearch采用的是Restful风格的API,因此其请求方式和路径相对都比较规范,而且请求参数也都采用JSON风格。

我们直接基于Kibana的DevTools来编写请求做测试,由于有语法提示,会非常方便。

.创建索引库和映射

基本语法

  • 请求方式:PUT

  • 请求路径:/索引库名,可以自定义

  • 请求参数:mapping映射

javascript 复制代码
PUT /索引库名称
{
  "mappings": {
    "properties": {
      "字段名":{
        "type": "text",
        "analyzer": "ik_smart"
      },
      "字段名2":{
        "type": "keyword",
        "index": "false"
      },
      "字段名3":{
        "properties": {
          "子字段": {
            "type": "keyword"
          }
        }
      },
      // ...略
    }
  }
}

查询索引库

基本语法

  • 请求方式:GET

  • 请求路径:/索引库名

  • 请求参数:无

比如:GET /item


修改索引库

倒排索引结构虽然不复杂,但是一旦数据结构改变(比如改变了分词器),就需要重新创建倒排索引,这简直是灾难。因此索引库一旦创建,无法修改mapping

虽然无法修改mapping中已有的字段,但是却允许添加新的字段到mapping中,因为不会对倒排索引产生影响。因此修改索引库能做的就是向索引库中添加新字段,或者更新索引库的基础属性。

语法:

javascript 复制代码
PUT /索引库名/_mapping
{
  "properties": {
    "新字段名":{
      "type": "integer"
    }
  }
}

删除索引库

语法:

  • 请求方式:DELETE

  • 请求路径:/索引库名

  • 请求参数:无


文档操作

有了索引库,接下来就可以向索引库中添加数据了。

4.1 新增文档(指定 ID)​

javascript 复制代码
PUT /product/_doc/1​

{​

"id": 1,​

"name": "华为Mate70",​

"price": 5999,​

"category": "手机"​

}​

新增文档(自动 ID)

java 复制代码
POST /product/_doc​
{​
  "id": 2,​
  "name": "小米14",​
  "price": 4299,​
  "category": "手机"​
}

4.3 查询文档(根据 ID)

java 复制代码
GET /product/_doc/1

全量更新文档(相当于覆盖原文档)

java 复制代码
PUT /product/_doc/1
{
  "id": 1,
  "name": "华为Mate70 Pro",
  "price": 6999,
  "category": "手机"
}

局部更新文档(推荐)

java 复制代码
POST /product/_update/1
{
  "doc": {
    "price": 6799
  }
}

删除文档

java 复制代码
DELETE /product/_doc/1

批处理

java 复制代码
POST _bulk
{ "index" : { "_index" : "test", "_id" : "1" } }
{ "field1" : "value1" }
{ "delete" : { "_index" : "test", "_id" : "2" } }
{ "create" : { "_index" : "test", "_id" : "3" } }
{ "field1" : "value3" }
{ "update" : {"_id" : "1", "_index" : "test"} }
{ "doc" : {"field2" : "value2"} }
  • index代表新增操作

    • _index:指定索引库名

    • _id指定要操作的文档id

    • { "field1" : "value1" }:则是要新增的文档内容

  • delete代表删除操作

    • _index:指定索引库名

    • _id指定要操作的文档id

  • update代表更新操作

    • _index:指定索引库名

    • _id指定要操作的文档id

    • { "doc" : {"field2" : "value2"} }:要更新的文档字段

总结

  • 创建文档:POST /{索引库名}/_doc/文档id { json文档 }

  • 查询文档:GET /{索引库名}/_doc/文档id

  • 删除文档:DELETE /{索引库名}/_doc/文档id

  • 修改文档:

    • 全量修改:PUT /{索引库名}/_doc/文档id { json文档 }

    • 局部修改:POST /{索引库名}/``_update``/文档id { "doc": ``{字段}``}


在java中访问客户端

引入依赖

XML 复制代码
<dependency>
    <groupId>org.elasticsearch.client</groupId>
    <artifactId>elasticsearch-rest-high-level-client</artifactId>
</dependency>

因为SpringBoot默认的ES版本是7.17.10,所以我们需要覆盖默认的ES版本

XML 复制代码
  <properties>
      <maven.compiler.source>11</maven.compiler.source>
      <maven.compiler.target>11</maven.compiler.target>
      <elasticsearch.version>7.12.1</elasticsearch.version>
  </properties>

初始化RestHighLevelClient:

java 复制代码
RestHighLevelClient client = new RestHighLevelClient(RestClient.builder(
        HttpHost.create("http://192.168.150.101:9200")
));
java 复制代码
package com.hmall.item.es;

import org.apache.http.HttpHost;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import java.io.IOException;

public class IndexTest {

    private RestHighLevelClient client;

    @BeforeEach
    void setUp() {
        this.client = new RestHighLevelClient(RestClient.builder(
                HttpHost.create("http://192.168.150.101:9200")
        ));
    }

    @Test
    void testConnect() {
        System.out.println(client);
    }

    @AfterEach
    void tearDown() throws IOException {
        this.client.close();
    }
}

RestClient操作索引库

代码分为三步:

  • 1)创建Request对象。

    • 因为是创建索引库的操作,因此Request是CreateIndexRequest
  • 2)添加请求参数

    • 其实就是Json格式的Mapping映射参数。因为json字符串很长,这里是定义了静态字符串常量MAPPING_TEMPLATE,让代码看起来更加优雅。
  • 3)发送请求

    • client.``indices``()方法的返回值是IndicesClient类型,封装了所有与索引库操作有关的方法。例如创建索引、删除索引、判断索引是否存在等

RestClient操作文档

新增文档

java 复制代码
  // 1.准备Request对象
    IndexRequest request = new IndexRequest("items").id(itemDoc.getId());
    // 2.准备Json文档
    request.source(doc, XContentType.JSON);
    // 3.发送请求
    client.index(request, RequestOptions.DEFAULT);
  • 1)创建Request对象,这里是IndexRequest,因为添加文档就是创建倒排索引的过程

  • 2)准备请求参数,本例中就是Json文档

  • 3)发送请求

查询文档

删除文档

java 复制代码
@Test
void testDeleteDocument() throws IOException {
    // 1.准备Request,两个参数,第一个是索引库名,第二个是文档id
    DeleteRequest request = new DeleteRequest("item", "100002644680");
    // 2.发送请求
    client.delete(request, RequestOptions.DEFAULT);
}

修改文档

修改我们讲过两种方式:

  • 全量修改:本质是先根据id删除,再新增

  • 局部修改:修改文档中的指定字段值

在RestClient的API中,全量修改与新增的API完全一致,判断依据是ID:

  • 如果新增时,ID已经存在,则修改

  • 如果新增时,ID不存在,则新增

这里不再赘述,我们主要关注局部修改的API即可。

.批量导入文档

java 复制代码
@Test
void testBulk() throws IOException {
    // 1.创建Request
    BulkRequest request = new BulkRequest();
    // 2.准备请求参数
    request.add(new IndexRequest("items").id("1").source("json doc1", XContentType.JSON));
    request.add(new IndexRequest("items").id("2").source("json doc2", XContentType.JSON));
    // 3.发送请求
    client.bulk(request, RequestOptions.DEFAULT);
}

总结

  • 初始化RestHighLevelClient

  • 创建XxxRequest。

    • XXX是IndexGetUpdateDeleteBulk
  • 准备参数(IndexUpdateBulk时需要)

  • 发送请求。

    • 调用RestHighLevelClient#.xxx()方法,xxx是indexgetupdatedeletebulk
  • 解析结果(Get时需要)


DSL查询

在 Elasticsearch 中,所有复杂查询都是通过 DSL(Domain Specific Language) 来实现的 。简单说:DSL = 用 JSON 写查询语句

所有查询都分为两类:

(1)查询上下文(Query Context)

会**计算相关性分数(_score)**用于:全文搜索、模糊匹配例:match、match_phrase、multi_match

(2)过滤上下文(Filter Context)

不计算分数,只判断 是 / 否速度极快,会自动缓存用于:精确匹配、范围、布尔过滤例:term、range、bool filter

最常用 DSL 查询

match 全文检索

对字段分词后匹配

javascript 复制代码
{
  "query": {
    "match": {
      "name": "拉杆箱"
    }
  }
}

查询结果一般格式

term 精确匹配(不分词)

用于 keyword、数字、布尔

javascript 复制代码
{
  "query": {
    "term": {
      "category": "手机"
    }
  }
}

range 范围查询

javascript 复制代码
{
  "query": {
    "range": {
      "price": {
        "gte": 3000,
        "lte": 8000
      }
    }
  }
}

搜索的关键字高亮

javascript 复制代码
GET /{索引库名}/_search
{
  "query": {
    "match": {
      "搜索字段": "搜索关键字"
    }
  },
  "highlight": {
    "fields": {
      "高亮字段名称": {
        "pre_tags": "<em>",
        "post_tags": "</em>"
      }
    }
  }
}

bool :组合查询包含四个子句:

  • must:必须满足(算分)
  • should:满足任意一个
  • must_not:必须不满足
  • filter:必须满足(过滤,不计分

示例

javascript 复制代码
{
  "query": {
    "bool": {
      "must": [
        { "match": { "name": "手机" } }
      ],
      "filter": [
        { "range": { "price": { "gte": 2000 } } }
      ]
    }
  }
}

分页、排序、指定返回字段

javascript 复制代码
{
  "from": 0,
  "size": 10,
  "_source": ["name", "price"],
  "sort": [
    { "price": { "order": "desc" } }
  ],
  "query": {
    "match_all": {}
  }
}

总结

查询的DSL是一个大的JSON对象,包含下列属性:

  • query:查询条件

  • fromsize:分页条件

  • sort:排序条件

  • highlight:高亮条件


Java中RestClient查询

  • 第一步,创建SearchRequest对象,指定索引库名

  • 第二步,利用request.source()构建DSL,DSL中可以包含查询、分页、排序、高亮等

    • query():代表查询条件,利用QueryBuilders.matchAllQuery()构建一个match_all查询的DSL
  • 第三步,利用client.search()发送请求,得到响应

request.source(),它构建的就是DSL中的完整JSON参数。其中包含了querysortfromsizehighlight等所有功能

QueryBuilders,其中包含了叶子查询复合查询

解析响应结果

elasticsearch返回的结果是一个JSON字符串,结构包含:

  • hits:命中的结果

    • total:总条数,其中的value是具体的总条数值

    • max_score:所有结果中得分最高的文档的相关性算分

    • hits:搜索结果的文档数组,其中的每个文档都是一个json对象

      • _source:文档中的原始数据,也是json对象

因此,我们解析响应结果,就是逐层解析JSON字符串,流程如下:

  • SearchHits:通过response.getHits()获取,就是JSON中的最外层的hits,代表命中的结果

    • SearchHits``#``getTotalHits().value:获取总条数信息

    • SearchHits#getHits():获取SearchHit数组,也就是文档数组

      • SearchHit#getSourceAsString():获取文档结果中的_source,也就是原始的json文档数据

叶子查询

所有的查询条件都是由QueryBuilders来构建的,叶子查询也不例外。因此整套代码中变化的部分仅仅是query条件构造的方式,其它不动。

match查询:

java 复制代码
@Test
void testMatch() throws IOException {
    // 1.创建Request
    SearchRequest request = new SearchRequest("items");
    // 2.组织请求参数
    request.source().query(QueryBuilders.matchQuery("name", "脱脂牛奶"));
    // 3.发送请求
    SearchResponse response = client.search(request, RequestOptions.DEFAULT);
    // 4.解析响应
    handleResponse(response);
}

multi_match查询

java 复制代码
@Test
void testMultiMatch() throws IOException {
    // 1.创建Request
    SearchRequest request = new SearchRequest("items");
    // 2.组织请求参数
    request.source().query(QueryBuilders.multiMatchQuery("脱脂牛奶", "name", "category"));
    // 3.发送请求
    SearchResponse response = client.search(request, RequestOptions.DEFAULT);
    // 4.解析响应
    handleResponse(response);
}

还有range查询:

java 复制代码
@Test
void testRange() throws IOException {
    // 1.创建Request
    SearchRequest request = new SearchRequest("items");
    // 2.组织请求参数
    request.source().query(QueryBuilders.rangeQuery("price").gte(10000).lte(30000));
    // 3.发送请求
    SearchResponse response = client.search(request, RequestOptions.DEFAULT);
    // 4.解析响应
    handleResponse(response);
}

term查询:

java 复制代码
@Test
void testTerm() throws IOException {
    // 1.创建Request
    SearchRequest request = new SearchRequest("items");
    // 2.组织请求参数
    request.source().query(QueryBuilders.termQuery("brand", "华为"));
    // 3.发送请求
    SearchResponse response = client.search(request, RequestOptions.DEFAULT);
    // 4.解析响应
    handleResponse(response);
}

复合查询

复合查询也是由QueryBuilders来构建,我们以bool查询为例,DSL和JavaAPI的对比如图:

java 复制代码
@Test
void testBool() throws IOException {
    // 1.创建Request
    SearchRequest request = new SearchRequest("items");
    // 2.组织请求参数
    // 2.1.准备bool查询
    BoolQueryBuilder bool = QueryBuilders.boolQuery();
    // 2.2.关键字搜索
    bool.must(QueryBuilders.matchQuery("name", "脱脂牛奶"));
    // 2.3.品牌过滤
    bool.filter(QueryBuilders.termQuery("brand", "德亚"));
    // 2.4.价格过滤
    bool.filter(QueryBuilders.rangeQuery("price").lte(30000));
    request.source().query(bool);
    // 3.发送请求
    SearchResponse response = client.search(request, RequestOptions.DEFAULT);
    // 4.解析响应
    handleResponse(response);
}

排序和分页

java 复制代码
@Test
void testPageAndSort() throws IOException {
    int pageNo = 1, pageSize = 5;

    // 1.创建Request
    SearchRequest request = new SearchRequest("items");
    // 2.组织请求参数
    // 2.1.搜索条件参数
    request.source().query(QueryBuilders.matchQuery("name", "脱脂牛奶"));
    // 2.2.排序参数
    request.source().sort("price", SortOrder.ASC);
    // 2.3.分页参数
    request.source().from((pageNo - 1) * pageSize).size(pageSize);
    // 3.发送请求
    SearchResponse response = client.search(request, RequestOptions.DEFAULT);
    // 4.解析响应
    handleResponse(response);
}

高亮

java 复制代码
@Test
void testHighlight() throws IOException {
    // 1.创建Request
    SearchRequest request = new SearchRequest("items");
    // 2.组织请求参数
    // 2.1.query条件
    request.source().query(QueryBuilders.matchQuery("name", "脱脂牛奶"));
    // 2.2.高亮条件
    request.source().highlighter(
            SearchSourceBuilder.highlight()
                    .field("name")
                    .preTags("<em>")
                    .postTags("</em>")
    );
    // 3.发送请求
    SearchResponse response = client.search(request, RequestOptions.DEFAULT);
    // 4.解析响应
    handleResponse(response);
}
private void handleResponse(SearchResponse response) {
    SearchHits searchHits = response.getHits();
    // 1.获取总条数
    long total = searchHits.getTotalHits().value;
    System.out.println("共搜索到" + total + "条数据");
    // 2.遍历结果数组
    SearchHit[] hits = searchHits.getHits();
    for (SearchHit hit : hits) {
        // 3.得到_source,也就是原始json文档
        String source = hit.getSourceAsString();
        // 4.反序列化
        ItemDoc item = JSONUtil.toBean(source, ItemDoc.class);
        // 5.获取高亮结果
        Map<String, HighlightField> hfs = hit.getHighlightFields();
        if (CollUtils.isNotEmpty(hfs)) {
            // 5.1.有高亮结果,获取name的高亮结果
            HighlightField hf = hfs.get("name");
            if (hf != null) {
                // 5.2.获取第一个高亮结果片段,就是商品名称的高亮值
                String hfName = hf.getFragments()[0].string();
                item.setName(hfName);
            }
        }
        System.out.println(item);
    }
}

数据聚合

聚合 = 对数据进行统计、分组、求和、平均值、最大值、最小值、计数。

你可以把它理解为:MySQL 的 group by + sum + avg + count 超级加强版。

聚合的基本结构

javascript 复制代码
{
  "size": 0,       // 不返回原始数据,只返回统计结果
  "aggs": {        // 聚合固定写 aggs
    "自定义名称": {
      "聚合类型": {
        "field": "字段名"
      }
    }
  }
}

聚合常见的有三类:

  • 桶( Bucket **)**聚合:用来对文档做分组

    • TermAggregation:按照文档字段值分组,例如按照品牌值分组、按照国家分组

    • Date Histogram:按照日期阶梯分组,例如一周为一组,或者一月为一组

  • 度量( Metric **)**聚合:用以计算一些值,比如:最大值、最小值、平均值等

    • Avg:求平均值

    • Max:求最大值

    • Min:求最小值

    • Stats:同时求maxminavgsum

  • 管道( pipeline **)**聚合:其它聚合的结果为基础做进一步运算

**注意:**参加聚合的字段必须是keyword、日期、数值、布尔类型


.Bucket聚合

例如我们要统计所有商品中共有哪些商品分类,其实就是以分类(category)字段对数据分组。category值一样的放在同一组,属于Bucket聚合中的Term聚合。

javascript 复制代码
GET /items/_search
{
  "size": 0, 
  "aggs": {
    "category_agg": {
      "terms": {
        "field": "category",
        "size": 20
      }
    }
  }
}
  • size:设置size为0,就是每页查0条,则结果中就不包含文档,只包含聚合

  • aggs:定义聚合

    • category_agg:聚合名称,自定义,但不能重复

      • terms:聚合的类型,按分类聚合,所以用term

        • field:参与聚合的字段名称

        • size:希望返回的聚合结果的最大数量

查询的结果:

嵌套聚合

先按分类分组 → 再求每组的平均价格

javascript 复制代码
GET /product/_search
{
  "size": 0,
  "aggs": {
    "group_by_category": {
      "terms": {
        "field": "category"
      },
      "aggs": {
        "avg_price": {
          "avg": {
            "field": "price"
          }
        }
      }
    }
  }
}

结果

手机 → 平均价格 5800

电脑 → 平均价格 7200

按价格区间统计(range 聚合)

电商最常用:价格分段统计

javascript 复制代码
GET /product/_search
{
  "size": 0,
  "aggs": {
    "price_range": {
      "range": {
        "field": "price",
        "ranges": [
          { "to": 3000 },
          { "from": 3000, "to": 6000 },
          { "from": 6000 }
        ]
      }
    }
  }
}

对搜索结果聚合。

例如,我想知道价格高于3000元的手机品牌有哪些

javascript 复制代码
GET /items/_search
{
  "query": {
    "bool": {
      "filter": [
        {
          "term": {
            "category": "手机"
          }
        },
        {
          "range": {
            "price": {
              "gte": 300000
            }
          }
        }
      ]
    }
  }, 
  "size": 0, 
  "aggs": {
    "brand_agg": {
      "terms": {
        "field": "brand",
        "size": 20
      }
    }
  }
}

求平均值(avg)

javascript 复制代码
GET /product/_search
{
  "size": 0,
  "aggs": {
    "avg_price": {
      "avg": {
        "field": "price"
      }
    }
  }
}

求和(sum)

javascript 复制代码
GET /product/_search
{
  "size": 0,
  "aggs": {
    "total_price": {
      "sum": {
        "field": "price"
      }
    }
  }
}

最大值、最小值(max /min)

javascript 复制代码
GET /product/_search
{
  "size": 0,
  "aggs": {
    "max_price": {
      "max": {
        "field": "price"
      }
    },
    "min_price": {
      "min": {
        "field": "price"
      }
    }
  }
}

例:

统计价格高于3000的手机品牌,形成一个个桶。对桶内的商品做运算,获取每个品牌价格的最小值、最大值、平均值。

javascript 复制代码
GET /items/_search
{
  "query": {
    "bool": {
      "filter": [
        {
          "term": {
            "category": "手机"
          }
        },
        {
          "range": {
            "price": {
              "gte": 300000
            }
          }
        }
      ]
    }
  }, 
  "size": 0, 
  "aggs": {
    "brand_agg": {
      "terms": {
        "field": "brand",
        "size": 20
      },
      "aggs": {
        "stats_meric": {
          "stats": {
            "field": "price"
          }
        }
      }
    }
  }
}

stat聚合,可以同时获取minmaxavg的结果。


.RestClient实现聚合

可以看到在DSL中,aggs聚合条件与query条件是同一级别,都属于查询JSON参数。因此依然是利用request.source()方法来设置。

不过聚合条件的要利用AggregationBuilders这个工具类来构造。DSL与JavaAPI的语法对比如下:

聚合结果与搜索文档同一级别,因此需要单独获取和解析。具体解析语法如下:

java 复制代码
@Test
void testAgg() throws IOException {
    // 1.创建Request
    SearchRequest request = new SearchRequest("items");
    // 2.准备请求参数
    BoolQueryBuilder bool = QueryBuilders.boolQuery()
            .filter(QueryBuilders.termQuery("category", "手机"))
            .filter(QueryBuilders.rangeQuery("price").gte(300000));
    request.source().query(bool).size(0);
    // 3.聚合参数
    request.source().aggregation(
            AggregationBuilders.terms("brand_agg").field("brand").size(5)
    );
    // 4.发送请求
    SearchResponse response = client.search(request, RequestOptions.DEFAULT);
    // 5.解析聚合结果
    Aggregations aggregations = response.getAggregations();
    // 5.1.获取品牌聚合
    Terms brandTerms = aggregations.get("brand_agg");
    // 5.2.获取聚合中的桶
    List<? extends Terms.Bucket> buckets = brandTerms.getBuckets();
    // 5.3.遍历桶内数据
    for (Terms.Bucket bucket : buckets) {
        // 5.4.获取桶内key
        String brand = bucket.getKeyAsString();
        System.out.print("brand = " + brand);
        long count = bucket.getDocCount();
        System.out.println("; count = " + count);
    }
}
相关推荐
yhdata2 小时前
一体式AR眼镜市场稳步上扬:现规模119.2亿元,未来六年年均增速20.4%锁定441.1亿元
大数据·网络·人工智能·ar
老邋遢2 小时前
干货篇|02. 纯AI Coding商业应用
java·人工智能
阴暗扭曲实习生2 小时前
135编辑器开放平台架构解析:企业级富文本接入方案的技术实现
java·开发语言·中间件
盐水冰2 小时前
【烘焙坊项目】后端搭建(7)- 套餐管理界面
java·学习
XiaoHu02072 小时前
C/C++数据结构与算法(第二弹)
java·开发语言·数据结构
Chan162 小时前
双非 Java 后端首次实习 | 个人经验分享总结
java·开发语言·spring boot·spring·java-ee·intellij-idea
InfiSight智睿视界2 小时前
执行型AI落地巡店场景:从流程自动化到管理闭环
大数据·人工智能·自动化
我就是全世界2 小时前
TextPecker:强化学习破解中文文本渲染失真难题
大数据·人工智能·机器学习
计算机徐师兄2 小时前
Java基于SSM的校园顺路代送微信小程序【附源码、文档说明】
java·微信小程序·ssm·校园顺路代送微信小程序·校园顺路代送·顺路代送微信小程序·java校园顺路代送微信小程序