SpringCloud —— Elasticsearch的DSL查询

一、前言

上一节我们讲了es的快速入门,以及如何操作索引库和文档,但是并没有使用到es的核心功能------DSL查询,当初我们使用es就是看重其查询功能的强大,但是在上一节中我们只尝试了(批量)查询单个文档,而如果要进行更加复杂的查询,这显然是不够的,比如我们可能需要筛选多重字段,或者对查询结果进行统计、对查询的关键词进行高亮显示等等......显然这些功能我们还没有实现,这一节我们就将通过使用DSL查询来实现这些需求。

二、DSL查询

1.叶子查询

叶子查询是最简单的查询,但是也是最基础的查询,后续的查询都是基于叶子查询建立的。

(1)全文检索查询

顾名思义,全文检索查询就是对索引库中的所有文档进行查询,这个查询也可以对某些字段添加查询条件。

例如:

如果想查询出所有文档:

bash 复制代码
# 查询所有
GET /items/_search
{
  "query": {
    "match_all": {}
  }
}

match查询:如果想查询商品名中含有"脱脂牛奶"的文档:

bash 复制代码
# match查询
GET /items/_search
{
  "query": {
    "match": {
      "name": "脱脂牛奶"
    }
  }
}

multi_match查询:是match的多字段版本,可以对多个字段进行查询,比如刚刚我们只查询了商品名 中含有"脱脂牛奶"的文档,而使用multi_match就可以查询商品名或类目名称中含有"脱脂牛奶"的文档了。

bash 复制代码
# mlti_match查询
GET /items/_search
{
  "query": {
    "multi_match": {
      "query": "脱脂牛奶",
      "fields": ["name","category_name"]
    }
  }
}

(2)精确查询

对于一些字段,我们不能够按照匹配度来查询,比如价格,我只想查价格为100的商品,那就真的需要查询出价格为100的商品,101或99都不行,这就需要使用精确查询了。

下面就是通过精确查询查询品牌名为德亚的所有商品:

bash 复制代码
# term精确查询
GET /items/_search
{
  "query": {
    "term": {
      "brand": {
        "value": "德亚"
      }
    }
  }
}

同样的,范围查询也是精确查询的一种,因为其对于上下限的要求是精确的:

bash 复制代码
# 范围查询
GET /items/_search
{
  "query": {
    "range": {
      "price": {
        "gte": 150000,
        "lte": 180000
      }
    }
  }
}

对多个文档id的查询也属于精确查询:

bash 复制代码
# ids查询
GET /items/_search
{
  "query": {
    "ids": {
      "values": ["4641661","4641663"]
    }
  }
}

2.复合查询

补充一个概念------算分,算分是对于查询结果相关度的打分,比如搜索"脱脂牛奶",查询出的结果是按照相关度的分数进行降序排列的,当然,这一功能也可以用于打广告,本质就是通过修改算分权重来将打广告文档的排序往前面提。

(1)bool查询

本质就是对叶子查询的逻辑组合。

其中,mustfilter都是表示逻辑 "与" 运算,也就是必须同时匹配他们所包含的条件。

但是must和filter略有不同,他们的主要区别在于是否算分 ,用must匹配的查询结果是参与算分的,如果需要按照相关度将查询结果排序就使用must。filter相反,但是由于算分是会消耗性能的,所以对于不需要参与算分的条件,尽量还是使用filter来筛选。

bash 复制代码
GET /items/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "name": "智能手机"
          }
        }
      ]
      , "filter": [
        {
          "term": {
            "brand": "华为"
          }
        },
        {
          "range": {
            "price": {
              "gte": 90000,
              "lte": 159900
            }
          }
        }
      ]
    }
  }
}

(2)排序查询

对查询的结果进行排序:

bash 复制代码
# 排序查询
GET /items/_search
{
  "query": {
    "match_all": {}
  
  }
  , 
  "sort": [
    {
      "sold": "desc"
    },
    {
      "price": "asc"
    }
  ]
}

(3)分页查询

对查询结果进行分页。

bash 复制代码
# 分页查询
GET /items/_search
{
  "query": {
    "match_all": {}
  
  }
  , 
  "sort": [
    {
      "sold": "desc"
    },
    {
      "price": "asc"
    }
  ],
  "from": 0,
  "size": 3
}

(4)高亮

对搜索的词添加上<em>标签,用于让前端进行高亮提示:

bash 复制代码
# 高亮
GET /items/_search
{
  "query": {
    "match": {
      "name": "脱脂牛奶"
    }
  },
  "highlight": {
    "fields": {
      "name": {
        "pre_tags": "<em>",
        "post_tags": "</em>"
      }
    }
  }
}

3.聚合

聚合 就是对查询结果做统计、分组、计算 的操作,相当于 SQL 里的 GROUP BYCOUNTSUMAVG 等功能。

(1)Bucket聚合(桶聚合)

类似sql中的分组查询,回顾一下sql中的分组查询,其实就是根据某个字段进行分组,比如用类目名称进行分组,就可以得到"牛奶"的为一组,"面包"的为一组等等......那么分好组了就可以统计了,比如"牛奶"组中有五个商品,"面包"组中有十个商品。

比如这里,我们将查询到的结果用类目名称和品牌名称进行分组(桶聚合):

bash 复制代码
# 聚合 1
GET /items/_search
{
  "size":0,
  "aggs": {
    "cate_agg": {
      "terms": {
        "field": "category",
        "size": 10
      }
    },
    "brand_agg": {
      "terms": {
        "field": "brand",
        "size": 10
      }
    }
  }
}

结果如下:

(2)带条件聚合

sql 复制代码
-- 查询平均年龄小于45的员工,并根据工作地址分组,获取员工数量大于等于2的工作地址
select workaddress,count(*) from emp where age < 29 group by workaddress having count(*) >= 2;

类似于上面的where部分。

这里我们用了一个bool查询来限制查到的类目和价格区间,同时利用品牌名聚合分组。

于是就能得到:每个价格高于3000手机品牌组

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

结果如下:

(3)Metric聚合

其实就是对每个桶中的组进行统计计算。

这里我们将刚刚的分组进行价格统计(stats属性表示将最大值、最小值、平均值等都求出来)

bash 复制代码
# 聚合 3
GET /items/_search
{
  "query": {
    "bool": {
      "filter": [
        {"term": {
          "category": "手机"
          }
        },
        {
          "range": {
            "price": {
              "gte": 300000
            }
          }
        }
      ]
    }
  }, 
  "size":0,
  "aggs": {
    "brand_agg": {
      "terms": {
        "field": "brand",
        "size": 10
      },
      "aggs": {
        "price_stats": {
          "stats": {
            "field": "price"
          }
        }
      }
    }
  }
}

结果如下:

三、Java客户端

刚刚的这些都是在图形化界面中的JSON格式数据,依旧是不能用于实际开发的,所以接下来我们将使用es提供的JavaAPI来将上述DSL查询用Java客户端的方式重新模拟一次。

1.查询

由于解析结果的方式都是固定的,所以这里我们将这些步骤提出来:

java 复制代码
 private static void parseResponseResult(SearchResponse response) {
        SearchHits searchHits = response.getHits();
        //4.1总条数
        long value = searchHits.getTotalHits().value;
        System.out.println("value = " + value);
        //4.2命中的数据
        SearchHit[] hits = searchHits.getHits();
        for (SearchHit hit : hits) {
            // 4.2.1 获取source结果
            String json = hit.getSourceAsString();
            //4.2.2 转成ItemDoc
            ItemDoc doc = JSONUtil.toBean(json, ItemDoc.class);
            System.out.println("doc = " + doc);
        }
    }

(1)全文检索查询

java 复制代码
@Test
    void testMatchAll() throws IOException {
        //1.创建request对象
        SearchRequest request = new SearchRequest("items");
        //2.配置request参数
        request.source()
                .query(QueryBuilders.matchAllQuery());//构建查询条件
        //3.发送请求
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);
        System.out.println("response = " + response);

        //4.解析结果
        parseResponseResult(response);
    }

(2)bool查询

java 复制代码
@Test
    void testSortAndPage() throws IOException {
        //1.创建request对象
        SearchRequest request = new SearchRequest("items");
        //2.组织DSL参数
        request.source().query(
                QueryBuilders
                        .boolQuery()
                        .must(QueryBuilders.matchQuery("name", "脱脂牛奶"))
                        .filter(QueryBuilders.termQuery("brand", "德亚"))
                        .filter(QueryBuilders.rangeQuery("price").lt(30000))
        );//构建查询条件

        //3.发送请求
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);

        //4.解析结果
        parseResponseResult(response);
    }

(3)排序和分页查询

java 复制代码
@Test
    void testSortAndPage() throws IOException {
        //0.模拟前端传递的分页参数
        int pageNo = 2, pageSize = 5;

        //1.创建request对象
        SearchRequest request = new SearchRequest("items");
        //2.组织DSL参数
        //2.1 query条件
        request.source()
                .query(QueryBuilders.matchAllQuery());//构建查询条件
        //2.2 分页
        request.source().from((pageNo - 1) * pageSize).size(pageSize);
        request.source()
                .sort("sold", SortOrder.DESC)
                .sort("price", SortOrder.ASC);

        //3.发送请求
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);

        //4.解析结果
        parseResponseResult(response);
    }

(4)高亮

高亮查询比较特殊一点,因为我们知道,高亮查询的结果中有源文本和高亮文本,如果按照先前的解析方式,我们解析出来的都是源文本,这是没有意义的,所以需要修改解析方式,让高亮文本覆盖源文本:

java 复制代码
@Test
    void testHighlight() throws IOException {
        //1.创建request对象
        SearchRequest request = new SearchRequest("items");
        //2.组织DSL参数
        //2.1 query条件
        request.source().query(QueryBuilders.matchQuery("name", "脱脂牛奶"));
        //2.2 高亮条件
        request.source().highlighter(SearchSourceBuilder.highlight().field("name"));

        //3.发送请求
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);

        //4.解析结果
        parseResponseResult(response);
    }
java 复制代码
private static void parseResponseResult(SearchResponse response) {
        SearchHits searchHits = response.getHits();
        //4.1总条数
        long value = searchHits.getTotalHits().value;
        System.out.println("value = " + value);
        //4.2命中的数据
        SearchHit[] hits = searchHits.getHits();
        for (SearchHit hit : hits) {
            // 4.2.1 获取source结果
            String json = hit.getSourceAsString();
            //4.2.2 转成ItemDoc
            ItemDoc doc = JSONUtil.toBean(json, ItemDoc.class);
            //4.3 处理高亮结果
            Map<String, HighlightField> hfs = hit.getHighlightFields();
            if (hfs != null && !hfs.isEmpty()) {
                //4.3.1 根据高亮字段名获取高亮结果
                HighlightField hf = hfs.get("name");
                //4.3.2 获取高亮结果,覆盖非高亮结果
                String hfName = hf.getFragments()[0].string();
                doc.setName(hfName);
            }
            System.out.println("doc = " + doc);
        }
    }

2.聚合

聚合这里也需要注意一下解析方式,先获取聚合,然后获取桶,最后遍历统计。

注意:要指定聚合类型
Terms****aggregation = aggregations.get(brandAggName);

java 复制代码
@Test
    void testAgg() throws IOException {
        //1.创建request对象
        SearchRequest request = new SearchRequest("items");
        //2.组织DSL参数
        //2.1 分页
        request.source().size(0);
        //2.2 聚合条件
        String brandAggName = "brandAgg";
        request.source().aggregation(
                AggregationBuilders.terms(brandAggName).field("brand").size(10)
        );

        //3.发送请求
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);

        //4.解析结果
        Aggregations aggregations = response.getAggregations();
        //4.1 根据聚合名称获取对应的聚合
        Terms aggregation = aggregations.get(brandAggName);
        //4.2 获取buckets
        List<? extends Terms.Bucket> buckets = aggregation.getBuckets();
        //4.3 遍历获取每一个bucket
        for (Terms.Bucket bucket : buckets) {
            System.out.println("brand:"+bucket.getKeyAsString());
            System.out.println("count:"+bucket.getDocCount());
        }
    }

结果如下:

四、总结

至此,黑马商城的项目大体完成,这个项目让我从单体架构转换为了微服务架构,其中又学习了大量的中间件,nacos、seata、sentinel、es、RabbitMQ,这些中间件都是帮助网页开发的一把好手,项目虽完,学习不止,共勉。

相关推荐
亚马逊云开发者1 小时前
你的 AI Agent 在裸奔吗?四层防护方案,从权限到审计一次讲透
java
意疏1 小时前
openJiuwen实战:用AsyncCallbackFramework为Agent增强器添加可观测性
java·服务器·前端
马士兵教育1 小时前
2026年IT行业基本预测!计算机专业学生就业编程语言Java/C/C++/Python该如何选择?
java·开发语言·c++·人工智能·python·面试·职场和发展
Book思议-2 小时前
顺序表和链表核心差异与优缺点详解
java·数据结构·链表
沪漂阿龙2 小时前
语义搜索与RAG:让搜索引擎真正理解你的意图,让AI告别“幻觉”
人工智能·搜索引擎
小杨的博客2 小时前
Java + Selenium实现浏览器打印功能
java·selenium
wefly20172 小时前
M3U8 播放调试天花板!m3u8live.cn纯网页无广告,音视频开发效率直接拉满
java·前端·javascript·python·音视频
兆子龙2 小时前
antd 组件也做了同款效果!深入源码看设计模式在前端组件库的应用
java·前端·架构
祁梦2 小时前
Redis从入门到入土 --- 黑马点评判断秒杀资格
java·后端