[ElasticSearch] DSL查询

🌸个人主页: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
感谢点赞与关注~~~

在这里插入图片描述

今天我们来研究以下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);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值