[ElasticSearch] DSL查询

news2025/6/8 20:16:39

🌸个人主页:https://blog.csdn.net/2301_80050796?spm=1000.2115.3001.5343
🏵️热门专栏:
🧊 Java基本语法(97平均质量分)https://blog.csdn.net/2301_80050796/category_12615970.html?spm=1001.2014.3001.5482
🍕 Collection与数据结构 (93平均质量分)https://blog.csdn.net/2301_80050796/category_12621348.html?spm=1001.2014.3001.5482
🧀线程与网络(97平均质量分) https://blog.csdn.net/2301_80050796/category_12643370.html?spm=1001.2014.3001.5482
🍭MySql数据库(96平均质量分)https://blog.csdn.net/2301_80050796/category_12629890.html?spm=1001.2014.3001.5482
🍬算法(97平均质量分)https://blog.csdn.net/2301_80050796/category_12676091.html?spm=1001.2014.3001.5482
🍃 Spring(97平均质量分)https://blog.csdn.net/2301_80050796/category_12724152.html?spm=1001.2014.3001.5482
🎃Redis(97平均质量分)https://blog.csdn.net/2301_80050796/category_12777129.html?spm=1001.2014.3001.5482
🐰RabbitMQ(97平均质量分) https://blog.csdn.net/2301_80050796/category_12792900.html?spm=1001.2014.3001.5482
💻 项目总结(97平均质量分) https://blog.csdn.net/2301_80050796/category_12936070.html?spm=1001.2014.3001.5482
🎈JVM(97平均质量分) https://blog.csdn.net/2301_80050796/category_12976744.html?spm=1001.2014.3001.5482
🔍ElasticSearch(96平均质量分) https://blog.csdn.net/2301_80050796/category_12978995.html?spm=1001.2014.3001.5482
感谢点赞与关注~~~

在这里插入图片描述

目录

  • 1. DSL查询
    • 1.1 快速入门
    • 1.2 叶子查询
      • 1.2.1 全文检索查询
      • 1.2.2 精确查询
    • 1.3 复合查询
      • 1.3.1 算分函数查询
      • 1.3.2 bool查询
    • 1.4 排序
    • 1.5 分页
      • 1.5.1 基础分页
      • 1.5.2 深度分页
    • 1.6 高亮
      • 1.6.1 高亮显示原理
      • 1.6.2 实现高亮
    • 1.7 总结
  • 2. RestClient查询
    • 2.1 快速入门
      • 2.2.1 发送请求
      • 2.1.2 解析响应结果
      • 2.1.3 总结
    • 2.2 叶子查询
    • 2.3 复合查询
    • 2.4 排序和分页
    • 2.5 高亮

今天我们来研究以下ElasticSearch的数据搜索功能,ElasticSearch提供了基于json的DSL语句来定义查询条件,其javaAPI就是在组织DSL条件.
因此我们先学习DSL的查询语法,在基于DSL来对照学习javaAPI,就会事半功倍.

1. DSL查询

ElasticSearch的查询可以分为两大类:

  • 叶子查询: 一般是在特定的字段里查询特定的值,属于简单查询,很少单独使用.
  • 复合查询: 以逻辑方式组合多个叶子查询或者更改叶子查询的行为方式.

1.1 快速入门

我们依然在Kibana的DevTools中学习查询的DSL语法,首先来看查询的语法结构:

GET /{索引库名}/_search
{
  "query": {
    "查询类型": {
      // .. 查询条件
    }
  }
}

说明: GET /{索引库名}/_search: 其中的_search是固定路径,不能更改.

例如,我们以最简单的无条件查询,无条件查询的类型是: match_all,因此查询语句如下:

GET /items/_search
{
  "query": {
    "match_all": {
      
    }
  }
}

由于match_all无条件,所以条件位置不写即可.
执行结果如下:
在这里插入图片描述
你会发现虽然是match_all,但是响应结果中并不会包含索引库中的所有文档,而是仅仅有10条,这是因为出于安全考虑,elasticSearch设置了默认的查询页数.

1.2 叶子查询

叶子查询的类型也可以做进一步细分,详情大家可以参考官方文档:
https://www.elastic.co/guide/en/elasticsearch/reference/7.12/query-dsl.html

如图:
在这里插入图片描述
这里列举一些常见的,例如:

  • 全文检索查询(Full Text Queries): 利用分词器对用户输入搜索条件先分词,得到词条,然后在利用倒排索引搜索词条,例如:
    • match
    • multi_match
  • 精确查询(Term-level queries): 不对用户输入搜索条件分词,根据字段内容精确值匹配,但是只能查找keyword,数值,日期,boolean类型的字段,比如:
    • ids
    • term
    • range
  • 地理坐标查询: 用于搜索地理位置,搜索方式很多,例如:
    • geo_bounding_box: 按照矩形搜索
    • geo_distance: 按点和半径搜索

1.2.1 全文检索查询

全文检索的种类也很多,详情可以参考官方文档:
https://www.elastic.co/guide/en/elasticsearch/reference/7.12/full-text-queries.html

以全文检索中的match为例,语法如下:

GET /{索引库名}/_search
{
  "query": {
    "match": {
      "字段名": "搜索条件"
    }
  }
}

示例:
在这里插入图片描述
match类似的语法还有multi_match,区别在于可以同时对多个字段字段进行搜索,而且多个字段都要满足,语法示例:

GET /{索引库名}/_search
{
  "query": {
    "multi_match": {
      "query": "搜索条件",
      "fields": ["字段1", "字段2"]
    }
  }
}

示例:
在这里插入图片描述

1.2.2 精确查询

精确查询,英文是Term-level query,顾名思义,词条级别的查询,也就是说不会对用户输入的搜索条件再分词,而是作为一个词条,与搜索的字段内容精确值匹配,因此推荐查找keyword,数值,日期,boolean类型的字段,例如:

  • id
  • price
  • 城市
  • 地名
  • 人名

等等,作为一个整体才有含义的字段.
详情可以查看官方文档:
https://www.elastic.co/guide/en/elasticsearch/reference/7.12/term-level-queries.html

以term为例,其语法如下:

GET /{索引库名}/_search
{
  "query": {
    "term": {
      "字段名": {
        "value": "搜索条件"
      }
    }
  }
}

在这里插入图片描述
当输入的搜索条件不是词条,而是短语是,由于不做分词,反而搜索不到:
在这里插入图片描述
再看接下来range查询,语法如下:

GET /{索引库名}/_search
{
  "query": {
    "range": {
      "字段名": {
        "gte": {最小值},
        "lte": {最大值}
      }
    }
  }
}

range是范围查询,对于范围筛选的关键字有:

  • gte: 大于等于
  • gt: 大于
  • lte: 小于等于
  • lt: 小于

示例:
在这里插入图片描述

1.3 复合查询

复合查询大致可以分为两类:

  • 第一类: 基于逻辑运算组合叶子查询,实现组合条件,例如
    • bool
  • 第二类: 基于某种算法修改查询时的文档相关性算分,从而改变文档排名,例如:
    • function_score
    • dis_max

其他复合查询以及相关语法可以参考官方文档:
https://www.elastic.co/guide/en/elasticsearch/reference/7.12/compound-queries.html

1.3.1 算分函数查询

当我们利用match查询时,文档结果会根据与搜索词条的关联度打分(_score),返回结果时按照分值降序排列.
例如我们搜索"手机",结果如下:
在这里插入图片描述
从es5.1开始,采用的相关性打分算法是BM25算法,公式如下:
在这里插入图片描述
基于这套公式,救护可以判断出某个文档与用户搜索的关键字之间的关联度,还是比较准确的,但是,在实际业务需求中,常常会有竞价排名的功能,不是相关度越高排名越靠前,而是基于掏的钱多的排名更加靠前.
例如在百度中搜索java培训,排名靠前的就是广告推广:
在这里插入图片描述
要想人为控制相关性算分,就需要利用elasticsearch中的function sorce查询了.
基本语法:
function socre查询中包含了四部分内容:

  • 原始查询条件: query部分,基于这个条件搜索文档,并且基于BM25算法给文档打分,原始算分.
  • 过滤条件: filter部分,复合该条件的文档才会重新算分.
  • 算分函数: 复合filter条件的文档要根据这个函数做运算,得到的函数算分(function score),有四种函数
    • weight: 函数结果是常量
    • field_value_factor: 以文档中的某个字段值作为函数结果
    • random_socre: 以随机数作为函数结果
    • script_score: 自定义算分函数算法
  • 运算模式: 算分函数的结果,原始查询的相关性算分,两者之间的运算方式,包括:
    • multiply: 相乘
    • replace: 用function socre替换query socre
    • 其他,例如: sum,avg,max,min

function socre的运行流程如下:

  1. 根据原始条件查询搜索文档,并且计算相关性算分,称为原始算分.
  2. 根据过滤条件,过滤文档
  3. 复合过滤条件的文档,基于算分函数运算,得到函数算分.
  4. 原始算分函数算分基于运算模式做运算,得到最终结果,作为相关性算分.

因此,其中的关键点是:

  • 过滤条件: 决定哪些文档的算分被修改
  • 算分函数: 决定函数算分的算法
  • 运算模式: 决定最终算分结果

示例: 给iPhone这个品牌的手机算分提高十倍,分析如下:

  • 过滤条件: 品牌必须为iPhone
  • 算分函数: 常量weight,值为10
  • 算分模式: 相乘multiply

对应代码如下:

GET /hotel/_search
{
  "query": {
    "function_score": {
      "query": {  .... }, // 原始查询,可以是任意条件
      "functions": [ // 算分函数
        {
          "filter": { // 满足的条件,品牌必须是Iphone
            "term": {
              "brand": "Iphone"
            }
          },
          "weight": 10 // 算分权重为2
        }
      ],
      "boost_mode": "multipy" // 加权模式,求乘积
    }
  }
}

1.3.2 bool查询

bool查询,即布尔查询,就是利用逻辑运算来组合一个或者多个子查询子句的组合.
bool查询支持的逻辑运算有:

  • must: 必须匹配每个子查询,类似于"与"
  • should; 选择性匹配子查询,类似"或"
  • must_not: 必须不匹配,不参与算分,类似于"非"
  • filter: 必须匹配,不参与算分(和must的区别).

bool查询语法如下:

GET /items/_search
{
  "query": {
    "bool": {
      "must": [
        {"match": {"name": "手机"}}
      ],
      "should": [
        {"term": {"brand": { "value": "vivo" }}},
        {"term": {"brand": { "value": "小米" }}}
      ],
      "must_not": [
        {"range": {"price": {"gte": 2500}}}
      ],
      "filter": [
        {"range": {"price": {"lte": 1000}}}
      ]
    }
  }
}

出于性能考虑,与搜索关键字无关的查询尽量采用must_notfilter逻辑运算,避免参与相关性算分.
例如黑马商城的搜索页面:
在这里插入图片描述
其中输入框的搜索条件肯定要参与相关性算分,可以采用match,但是价格范围过滤,品牌过滤,分类过滤尽量采用filter,不要参与相关性算分.
比如,我们要搜索"手机",但是品牌必须是"华为",价格必须是900~1599,那么可以这样写:

GET /items/_search
{
  "query": {
    "bool": {
      "must": [
        {"match": {"name": "手机"}}
      ],
      "filter": [
        {"term": {"brand": { "value": "华为" }}},
        {"range": {"price": {"gte": 90000, "lt": 159900}}}
      ]
    }
  }
}

1.4 排序

elasticSearch默认是根据相关度算分(_socre)来排序,但是也支持自定方式对搜索结果排序.不过分词字段无法排序,能参与排序字段类型有: keyword类型,数值类型,地理坐标类型,日期类型.
详细说明可以参考官方文档:
https://www.elastic.co/guide/en/elasticsearch/reference/7.12/sort-search-results.html
语法说明:

GET /indexName/_search
{
  "query": {
    "match_all": {}
  },
  "sort": [
    {
      "排序字段": {
        "order": "排序方式asc和desc"
      }
    }
  ]
}

示例,我们按照商品价格排序:

GET /items/_search
{
  "query": {
    "match_all": {}
  },
  "sort": [
    {
      "price": {
        "order": "desc"
      }
    }
  ]
}

1.5 分页

elasticsearch默认情况下只返回top10的数据.而如果要查询的更多数据就需要修改分页参数了.

1.5.1 基础分页

elasticSearch中通过修改form,size参数来控制要返回的分页结果:

  • from: 从第几个文档开始
  • size: 总共查询几个文档

类似于MySQL中的limit ?,?,官方文档如下:
官方文档如下:
https://www.elastic.co/guide/en/elasticsearch/reference/7.12/paginate-search-results.html
语法如下:

GET /items/_search
{
  "query": {
    "match_all": {}
  },
  "from": 0, // 分页开始的位置,默认为0
  "size": 10,  // 每页文档数量,默认10
  "sort": [
    {
      "price": {
        "order": "desc"
      }
    }
  ]
}

1.5.2 深度分页

elasticSearch的数据一般会采用分片存储,也就是把一个索引中的数据分成N份,存储到不同的节点上,这种存储方式比较有利于数据扩展,但是给分页带来了一些麻烦.
比如一个索引库中有100000条数据,分别存储到4个分片,每个分片25000条数据.现在每页查询10条,查询99页,那么分页查询的条件如下:

GET /items/_search
{
  "from": 990, // 从第990条开始查询
  "size": 10, // 每页查询10条
  "sort": [
    {
      "price": "asc"
    }
  ]
}

从语句来分析,要查询第990-1000名的数据.从数显思路来分析,坑定是将所有数据排序,找出前1000名,截取其中的990-1000的部分,但是问题来了,我们如何才能找到所有数据中的前1000名呢?
要知道每一片的数据都不一样,第一片上的第990-1000,在另一个节点上并不一定依然是990-1000名,所以我们只能在每一个分片上都要找出排名前1000的数据,然后汇总到一起,重新排序,才能找出整个索引库中真正的前1000名,此时截取的990-1000的数据即可.

就像我们高中的时候有普通班,有尖子班,比如有5个班,其中有一个尖子班,我们找全年级前50的时候,肯定不能只挑每个班的前10名,全年级前50肯定是尖子班多一些,所以我们在选人的时候需要对全年级进行排名.在这里插入图片描述

试想一下,加入我们现在要查询的是第999页数据呢,是不是要找地9990-10000的数据,那岂不是要把每个分片中的前10000名的数据都查询出来,汇总到一起,在内存中排序?如果查询的分页深度更深呢,需要一次检索的数据岂不是更多?
由此可知,当查询分页深度较大时,汇总的数据过多,对内存和CPU会产生非常大的压力.
因此elasticSearch会禁止from+ size超过10000的请求.
针对深度分页,elasticSearch提供了两种解决方案:

  • Search after: 分页时需要排序,原理是从上一次的排序开始,查询下一页数据,官方推荐使用的方式.
  • scroll: 原理将排序后的文档id形成快照,保存下来,基于快照做分页,官方已经不推荐使用.

总结:
大多数情况下,我们采用普通分页就是可以了,一般我们采用限制分页深度的方式即可,无需实现深度分页.

1.6 高亮

1.6.1 高亮显示原理

什么是高亮显示呢?
我们在百度,京东搜索的时候,关键字就会变成红色,比较醒目,这就叫高亮显示:
在这里插入图片描述
观察页面源码,你会发现两件事情:

  • 高亮词条都被加上的了<em>标签.
  • <em>标签都添加了红色样式.

css样式肯定是前端实现页面的时候写好的,但是前端编写页面的时候是不知道页面要展示什么数据的,不可能给数据加标签,而服务端实现搜索功能,要是有elasticSearch做分词搜索,要是知道哪些词条需要高亮的.
因此词条的高亮标签肯定是有服务端提供数据的时候已经加上的.

因此实现高亮的思路就是:

  • 用户搜索关键字搜索数据
  • 服务端根据搜索关键字找到elasticSearch搜索,并给搜索结果中的关键字词条添加一个html标签
  • 前端提前给约定好的html标签添加css样式.

1.6.2 实现高亮

事实上elasticSearch已经提供了给搜索关键字加标签的语法,无需我们自己编码.基本语法如下:

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

注意:

  • 搜索必须有查询条件,而且是全文检索类型的查询条件,例如match
  • 参与高亮的字段必须是text类型的字段.
  • 默认情况下参与高亮的字段要与搜索字段一致,除非添加 required_field_match=false.

示例:
在这里插入图片描述

1.7 总结

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

  • query: 查询条件
  • from和size: 分页条件
  • sort: 排序条件
  • hightlight: 高亮条件

示例:
在这里插入图片描述

2. RestClient查询

文档的查询依然使用上一节学习的RestHighLevelClient对象,查询的基本步骤如下:

  • 创建request对象,这次是搜索,所以是searchRequest
  • 准备请求参数,也就是查询DSL对应的json参数
  • 发起请求
  • 解析响应,响应对应的结果相对复杂,需要逐层解析

2.1 快速入门

之前说过,由于elasticSearch对外暴露的接口都是restful风格的接口,因此JavaAPI调用就是在发送http请求,而我们核心要做的就是利用Java代码组织请求参数,解析响应结果.
这个参数的格式完全参考DSL查询语句的json结构,因此我们在学习的过程中,会不断的把JavaAPI与DSL语句做对比.

2.2.1 发送请求

首先以match_all查询为例,其DSL和JavaAPI的对比如图:
在这里插入图片描述
代码解读:

  • 第一步,创建SearchRequest对象,指定索引库名
  • 第二部,利用request.source()创建DSL,DSL中可以包含查询,分页,排序,高亮等
    • query(): 代表查询条件,利用QueryBuilders.matchAllQuery()构建一个match_all查询的DSL.
  • 第三步,利用client.search()发送请求,得到响应

这里关键的API有两个,一个是request.source(),它构建的就是DSL中的完整的json参数,其中包含了query、sort、from、size、highlight等所有功能:
在这里插入图片描述
另一个是QueryBuilders,其中包含了我们学习过的各种叶子查询,复合查询等:
在这里插入图片描述

2.1.2 解析响应结果

在发送请求以后,得到了相应的结果SearchResponse,这个类的结构与我们在kibana中看到的相应结果json接口完全一致:

{
    "took" : 0,
    "timed_out" : false,
    "hits" : {
        "total" : {
            "value" : 2,
            "relation" : "eq"
        },
        "max_score" : 1.0,
        "hits" : [
            {
                "_index" : "heima",
                "_type" : "_doc",
                "_id" : "1",
                "_score" : 1.0,
                "_source" : {
                "info" : "Java讲师",
                "name" : "赵云"
                }
            }
        ]
    }
}

因此,我们解析SearchResponse的代码就是在解析这个json结果,对比如下:
在这里插入图片描述
代码解读:
elasticsearch返回的结果是一个json字符串,结构包含:

  • hit: 命中的结果
    • total: 总条数,其中value是具体的总条数值
    • max_score: 所有的结果中得分最高的文档的相关性算分
    • hit: 搜索结果的文档数组,其中的每个文档都是json对象
      • _source: 文档中的原始数据,也是json对象

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

  • SearchHits: 通过response.getHits()获取,就是json中最外层的hits,代表命中的结果
    • SearchHits#getTotalHits().value: 获取总条数信息
    • SearchHits#getHits(): 获取SearchHit数组,也就是文档数组
      • SearchHit#getSourceAsString(): 获取文档结果中的_source,也就是原始的json文档数据

2.1.3 总结

文档搜索的基本步骤是:

  1. 创建SearchRequest对象
  2. 准备request.source(),也就是DSL.
    • QueryBuilders来构建查询条件
    • 传入request.source()query()方法
  3. 发送请求,得到结果
  4. 解析结果(参考json结果,从外到内,逐层解析)

完整代码如下:

@Test
void testMatchAll() throws IOException {
    // 1.创建Request
    SearchRequest request = new SearchRequest("items");
    // 2.组织请求参数
    request.source().query(QueryBuilders.matchAllQuery());
    // 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);
        System.out.println(item);
    }
}

2.2 叶子查询

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

@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查询:

@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查询:

@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查询:

@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);
}

2.3 复合查询

复合查询也是由QueryBuilders构建,我们以bool查询为例,DSL和javaAPI的对比如图:
在这里插入图片描述
完整代码如下:

@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);
}

2.4 排序和分页

之前说过,requeset.source()就是整个请求json参数,所以排序,分页都是基于这个来设置,其DSL和JavaAPI的对比如下:
在这里插入图片描述
完整的示例代码:

@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);
}

2.5 高亮

高亮查询与前面的查询有两点不同:

  • 条件同样是在request.source()中指定,只不过高亮条件要基于HighlightBuilder来构造
  • 高亮相应结果与搜索的文档结果不在一起,需要单独解析

首先来看高亮条件构造,其DSL和JavaAPI的对比如图:
在这里插入图片描述
示例代码如下:

@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);
}

再来看结果解析,文档解析的部分不变,主要是高亮内容需要单独解析出来,其DSL和JavaAPI的对比如图:
在这里插入图片描述
代码解读:

  • 第3、4步:从结果中获取_source。hit.getSourceAsString(),这部分是非高亮结果,json字符串。还需要反序列为ItemDoc对象
  • 第5步:获取高亮结果。hit.getHighlightFields(),返回值是一个Map,key是高亮字段名称,值是HighlightField对象,代表高亮值
  • 第5.1步:从Map中根据高亮字段名称,获取高亮字段值对象HighlightField
  • 第5.2步:从HighlightField中获取Fragments,并且转为字符串。这部分就是真正的高亮字符串了
  • 最后:用高亮的结果替换ItemDoc中的非高亮结果

完整代码如下:

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);
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2404522.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

iview中的table组件点击一行中的任意一点选中本行

<Table border ref"selection" size"small" on-row-click"onClickRow"></Table>// table组件点击一行任意位置选中onClickRow(row, index) {this.$refs.selection.toggleSelect(index)}写上toggleSelect(index)方法即可&#xff0c;…

《探秘跨网段局域网IP广播:解锁网络通信的新姿势》

一、从基础出发:广播与跨网段 在计算机网络的世界中,广播域是一个至关重要的概念。简单来说,广播域是指网络中能接收任一台主机发出的广播帧的所有主机集合。当一台主机在广播域内发出一个广播帧时,同一广播域内的所有其他主机都可以收到该广播帧。在没有路由器或 VLAN 分割…

maven微服务${revision}依赖打包无法识别

1、场景描述 我现在又一个微服务项目&#xff0c;父pom的版本&#xff0c;使用<properties>定义好&#xff0c;如下所示&#xff1a; <name>ypsx-finance-center</name> <artifactId>ypsx-finance</artifactId> <packaging>pom</pack…

2025年06月07日Github流行趋势

项目名称&#xff1a;netbird 项目地址url&#xff1a;https://github.com/netbirdio/netbird项目语言&#xff1a;Go历史star数&#xff1a;14824今日star数&#xff1a;320项目维护者&#xff1a;mlsmaycon, braginini, pascal-fischer, lixmal, pappz项目简介&#xff1a;使…

WPS中将在线链接转为图片

WPS中将在线链接转为图片 文章目录 WPS中将在线链接转为图片一&#xff1a;解决方案1、下载图片&#xff0c;精确匹配&#xff08;会员功能&#xff09;2、将在线链接直接转为图片 一&#xff1a;解决方案 1、下载图片&#xff0c;精确匹配&#xff08;会员功能&#xff09; …

实战二:开发网页端界面完成黑白视频转为彩色视频

​一、需求描述 设计一个简单的视频上色应用&#xff0c;用户可以通过网页界面上传黑白视频&#xff0c;系统会自动将其转换为彩色视频。整个过程对用户来说非常简单直观&#xff0c;不需要了解技术细节。 效果图 ​二、实现思路 总体思路&#xff1a; 用户通过Gradio界面上…

vue生成二维码图片+文字说明

需求&#xff1a;点击下载图片&#xff0c;上方是二维码&#xff0c;下方显示该二维码的相关内容&#xff0c;并且居中显示&#xff0c;支持换行 解决方案步骤&#xff1a; 1. 使用qrcode生成二维码的DataURL。 2. 创建canvas&#xff0c;将二维码图片绘制到canvas的上半部分…

机器学习监督学习实战五:六种算法对声呐回波信号进行分类

本项目基于UCI的声呐目标识别数据集&#xff08;Sonar, Mines vs. Rocks&#xff09;&#xff0c;通过10种机器学习算法比较&#xff0c;发现集成学习方法表现最优。研究首先对60个声呐能量特征进行可视化分析&#xff08;分布直方图、相关性矩阵&#xff09;&#xff0c;对比了…

​React Hooks 的闭包陷阱问题

这是主包在面试中遇到的一道题目&#xff0c;面试官的问题是&#xff1a;"这个页面初次展示出来时Count和step的值是什么&#xff0c;我点击按钮count和step的值有什么变化&#xff1f;“ 这个题目主包回答的不好&#xff0c;所以想做一个总结。 题目 import React, { …

力扣面试150题--克隆图

Day 61 题目描述 思路 /* // Definition for a Node. class Node {public int val;public List<Node> neighbors;public Node() {val 0;neighbors new ArrayList<Node>();}public Node(int _val) {val _val;neighbors new ArrayList<Node>();}public N…

鸿蒙PC,有什么缺点?

点击上方关注 “终端研发部” 设为“星标”&#xff0c;和你一起掌握更多数据库知识 价格太高&#xff0c;二是部分管理员权限首先&#xff0c;三对于开发者不太友好举个例子&#xff1a;VSCode的兼容性对程序员至关重要。若能支持VSCode&#xff0c;这台电脑将成为大多数开发者…

PDF图片和表格等信息提取开源项目

文章目录 综合性工具专门的表格提取工具经典工具 综合性工具 PDF-Extract-Kit - opendatalab开发的综合工具包&#xff0c;包含布局检测、公式检测、公式识别和OCR功能 仓库&#xff1a;opendatalab/PDF-Extract-Kit特点&#xff1a;功能全面&#xff0c;包含表格内容提取的S…

《Progressive Transformers for End-to-End Sign Language Production》复现报告

摘要 本文复现了《Progressive Transformers for End-to-End Sign Language Production》一文中的核心模型结构。该论文提出了一种端到端的手语生成方法&#xff0c;能够将自然语言文本映射为连续的 3D 骨架序列&#xff0c;并引入 Counter Decoding 实现动态序列长度控制。我…

计算机视觉——相机标定

计算机视觉——相机标定 一、像素坐标系、图像坐标系、相机坐标系、世界坐标系二、坐标系变换图像坐标系 → 像素坐标系相机坐标系 → 图像坐标系世界坐标系 → 相机坐标系 ⋆ \star ⋆ 世界坐标系 → 像素坐标系 三、相机标定 一、像素坐标系、图像坐标系、相机坐标系、世界坐…

C语言中的数据类型(二)--结构体

在之前我们已经探讨了C语言中的自定义数据类型和数组&#xff0c;链接如下&#xff1a;C语言中的数据类型&#xff08;上&#xff09;_c语言数据类型-CSDN博客 目录 一、结构体的声明 二、结构体变量的定义和初始化 三、结构体成员的访问 3.1 结构体成员的直接访问 3.2 结…

C++11:原子操作与内存顺序:从理论到实践的无锁并发实现

文章目录 0.简介1.并发编程需要保证的特性2.原子操作2.1 原子操作的特性 3.内存顺序3.1 顺序一致性3.2 释放-获取&#xff08;Release-Acquire)3.3 宽松顺序&#xff08;Relaxed)3.4 内存顺序 4.无锁并发5. 使用建议 0.简介 在并发编程中&#xff0c;原子性、可见性和有序性是…

动力电池点焊机:驱动电池焊接高效与可靠的核心力量|比斯特自动化

在新能源汽车与储能设备需求激增的背景下&#xff0c;动力电池的制造工艺直接影响产品性能与安全性。作为电芯与极耳连接的核心设备&#xff0c;点焊机如何平衡效率、精度与可靠性&#xff0c;成为电池企业关注的重点。 动力电池点焊机的核心功能是确保电芯与极耳的稳固连接。…

【MySQL】10.事务管理

1. 事务的引入 首先我们需要知道CURD操作不加控制会产生什么问题&#xff1a; 为了解决上面的问题&#xff0c;CURD需要满足如下条件&#xff1a; 2. 事务的概念 事务就是一组DML语句组成&#xff0c;这些语句在逻辑上存在相关性&#xff0c;这一组DML语句要么全部成功&…

Bugku-CTF-Web安全最佳刷题路线

曾经的我也是CTF六项全能&#xff0c;Web安全&#xff0c;密码学&#xff0c;杂项&#xff0c;Pwn&#xff0c;逆向&#xff0c;安卓样样都会。明明感觉这样很酷&#xff0c;却为何还是沦为社畜。Bugku-CTF-Web安全最佳刷题路线&#xff0c;我已经整理好了&#xff0c;干就完了…

IT学习方法与资料分享

一、编程语言与核心技能&#xff1a;构建技术地基 1. 入门首选&#xff1a;Python 与 JavaScript Python&#xff1a;作为 AI 与数据科学的基石&#xff0c;可快速构建数据分析与自动化脚本开发能力。 JavaScript&#xff1a;Web 开发的核心语言&#xff0c;可系统掌握 React/V…