分布式搜索elasticsearch

文章详细介绍了Elasticsearch的倒排索引概念,以及如何使用IK分词器处理中文搜索。此外,还讨论了如何通过RestHighLevelClient进行索引库和文档的操作,包括创建、查看、删除和修改。同时,文章涵盖了DSL查询语法,包括全文检索、精确查询、地理查询和复合查询等,并展示了如何使用Java的RestHighLevelClient执行这些查询。
摘要由CSDN通过智能技术生成

分布式搜索elasticsearch

elasticsearch是一款非常强大的开源搜索引擎,可以帮助我们从海量数据中快速找到需要的内容。

elasticsearch结合kibana、Logstash、Beats,也就是elastic stack (ELK)。被广泛应用在日志数据分析、实时监控等领域
elasticsearch是elastic stack的核心,负责存储、搜索、分析数据。

正向索引和倒排索引

传统数据库(如MySQL)采用正向索引,例如给下表(tb_goods)中的id创建索引:

image-20230607155737265

elasticsearch采用倒排索引:

  • 文档( document) :每条数据就是一个文档
  • 词条(term) :文档按照语义分成的词语

image-20230607155959261

image-20230607160050771

文档

elasticsearch是面向文档存储的,可以是数据库中的一条商品数据,一个订单信息。

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

索引

索引(index):相同类型的文档的集合

映射(mapping)︰索引中文档的字段约束信息,类似表的结构约束

MySQLElasticsearch说明
TableIndex索引(index),就是文档的集合,类似数据库的表(table)
RowDocument文档(Document),就是一条条的数据,类似数据库中的行(Row),文档都是JSON格式
ColumnField字段(Field),就是JSON文档中的字段,类似数据库中的列(Column)
schemaMappingMapping(映射)是索引中文档的约束,例如字段类型约束。类似数据库的表结构(Schema)
SQLDSLDSL是elasticsearch提供的JSON风格的请求语句,用来操作elasticsearch,实现CRUD

分词器

es在创建倒排索引时需要对文档分词;在搜索时,需要对用户输入内容分词。但默认的分词规则对中文处理并不友好。我们在kibana的DevTools中测试:

# 测试分词器
POST /_analyze
{
  "text": "黑马程序员学习java太棒了",
  "analyzer": "english"
}

结果

{
  "tokens" : [
    {
      "token" : "黑",
      "start_offset" : 0,
      "end_offset" : 1,
      "type" : "<IDEOGRAPHIC>",
      "position" : 0
    },
    {
      "token" : "马",
      "start_offset" : 1,
      "end_offset" : 2,
      "type" : "<IDEOGRAPHIC>",
      "position" : 1
    },
    {
      "token" : "程",
      "start_offset" : 2,
      "end_offset" : 3,
      "type" : "<IDEOGRAPHIC>",
      "position" : 2
    },
    {
      "token" : "序",
      "start_offset" : 3,
      "end_offset" : 4,
      "type" : "<IDEOGRAPHIC>",
      "position" : 3
    },
    {
      "token" : "员",
      "start_offset" : 4,
      "end_offset" : 5,
      "type" : "<IDEOGRAPHIC>",
      "position" : 4
    },
    {
      "token" : "学",
      "start_offset" : 5,
      "end_offset" : 6,
      "type" : "<IDEOGRAPHIC>",
      "position" : 5
    },
    {
      "token" : "习",
      "start_offset" : 6,
      "end_offset" : 7,
      "type" : "<IDEOGRAPHIC>",
      "position" : 6
    },
    {
      "token" : "java",
      "start_offset" : 7,
      "end_offset" : 11,
      "type" : "<ALPHANUM>",
      "position" : 7
    },
    {
      "token" : "太",
      "start_offset" : 11,
      "end_offset" : 12,
      "type" : "<IDEOGRAPHIC>",
      "position" : 8
    },
    {
      "token" : "棒",
      "start_offset" : 12,
      "end_offset" : 13,
      "type" : "<IDEOGRAPHIC>",
      "position" : 9
    },
    {
      "token" : "了",
      "start_offset" : 13,
      "end_offset" : 14,
      "type" : "<IDEOGRAPHIC>",
      "position" : 10
    }
  ]
}

中文词汇没有正常分开

使用IK分词器

IK分词器包含两种模式:

  • ik_smart:最少切分

  • ik_max_word:最细切分

GET /_analyze
{
  "analyzer": "ik_max_word",
  "text": "黑马程序员学习java太棒了"
}

结果

{
  "tokens" : [
    {
      "token" : "黑马",
      "start_offset" : 0,
      "end_offset" : 2,
      "type" : "CN_WORD",
      "position" : 0
    },
    {
      "token" : "程序员",
      "start_offset" : 2,
      "end_offset" : 5,
      "type" : "CN_WORD",
      "position" : 1
    },
    {
      "token" : "程序",
      "start_offset" : 2,
      "end_offset" : 4,
      "type" : "CN_WORD",
      "position" : 2
    },
    {
      "token" : "员",
      "start_offset" : 4,
      "end_offset" : 5,
      "type" : "CN_CHAR",
      "position" : 3
    },
    {
      "token" : "学习",
      "start_offset" : 5,
      "end_offset" : 7,
      "type" : "CN_WORD",
      "position" : 4
    },
    {
      "token" : "java",
      "start_offset" : 7,
      "end_offset" : 11,
      "type" : "ENGLISH",
      "position" : 5
    },
    {
      "token" : "太棒了",
      "start_offset" : 11,
      "end_offset" : 14,
      "type" : "CN_WORD",
      "position" : 6
    },
    {
      "token" : "太棒",
      "start_offset" : 11,
      "end_offset" : 13,
      "type" : "CN_WORD",
      "position" : 7
    },
    {
      "token" : "了",
      "start_offset" : 13,
      "end_offset" : 14,
      "type" : "CN_CHAR",
      "position" : 8
    }
  ]
}

ik分词器-拓展词库

要拓展ik分词器的词库,只需要修改一个ik分词器目录中的config目录中的IkAnalyzer.cfg.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>
	 <!--用户可以在这里配置自己的扩展停止词字典-->
	<entry key="ext_stopwords">stopword.dic</entry>
	<!--用户可以在这里配置远程扩展字典 -->
	<!-- <entry key="remote_ext_dict">words_location</entry> -->
	<!--用户可以在这里配置远程扩展停止词字典-->
	<!-- <entry key="remote_ext_stopwords">words_location</entry> -->
</properties>

加入的ext.dic和stopword.dic就是加入的扩展词文件和停止扩展词文件

在config目录新建ext.dic文件即可,stopword.dic已经有文件了

索引库操作

mapping属性

mapping是对索引库中文档的约束,常见的mapping属性包括:

  • type:字段数据类型,常见的简单类型有:

    • 字符串: text(可分词的文本)、keyword(精确值,例如:品牌、国家、ip地址)
    • 数值: long、integer、short、byte、double、float、
    • 布尔: boolean
    • 日期:date
    • 对象:object
  • index:是否创建索引,默认为true

  • analyzer:使用哪种分词器

  • properties:该字段的子字段

创建索引库

ES中通过Restful请求操作索引库、文档。请求内容用DSL语句来表示。创建索引库和mapping的DSL语法如下:

image-20230607195506629

查看、删除索引库

查看索引库语法:

  • GET/索引库名

示例:

  • GET /heima

删除索引库的语法:

  • DELETE/索引库名

示例:

  • DELETE /heima

修改索引库

索引库和mapping一旦创建无法修改,但是可以添加新的字段,语法如下:

image-20230607200050971

文档操作

新增文档的DSL语法如下:

image-20230607200342073

查看、删除文档

查看文档语法:

  • GET /索引库名/_doc/文档id

示例

  • GET /heima/ _doc/1

删除索引库的语法:

  • DELETE /索引库名/_doc/文档id

示例:

  • DELETE /heima/ _doc/1

修改文档

方式一:全量修改,会删除旧文档,添加新文档

image-20230607200827707

image-20230607200838249

方式二:增量修改,修改指定字段值

image-20230607200937642

image-20230607200954618

RestClient操作索引库

1.引入es的RestHighLevelclient依赖:

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

2.因为springboot默认是elasticsearch默认的版本是7.6.1,所以需要在properties标签中覆盖,父工程提供的默认版本

<properties>
    <java.version>1.8</java.version>
    <elasticsearch.version>7.12.1</elasticsearch.version>
</properties>

3.初始化RestHighLevelClient:

this.client = new RestHighLevelClient(RestClient.builder(
                HttpHost.create("http://192.168.1.128:9200")
        ));

创建索引库

image-20230608151736965

删除索引库

@Test
public void deleteHotelIndex() throws IOException {
    //创建request对象
    DeleteIndexRequest request = new DeleteIndexRequest("hotel");
    //发送请求
    client.indices().delete(request, RequestOptions.DEFAULT);
}

判断索引库是否存在

@Test
public void existHotelIndex() throws IOException {
    //创建request对象
    GetIndexRequest request = new GetIndexRequest("hotel");
    //发送请求
    boolean exists = client.indices().exists(request, RequestOptions.DEFAULT);
    System.out.println(exists);
}

索引库操作的基本步骤:

  1. 初始化RestHighLevelClient
  2. 创建XxxlndexRequest。XXX是CREATE、Get、Delete
  3. 准备DSL (CREATE时需要)
  4. 发送请求。调用RestHighLevelClient#indices().xxx()方法,XXX是create、exists、delete

RestClient操作文档

新增文档

@Test
public void addDocumentTest() throws IOException {
    //根据id查询酒店数据
    Hotel hotel = iHotelService.getById(61083L);
    //转换为文档类型
    HotelDoc hotelDoc = new HotelDoc(hotel);

    //准备Request对象
    IndexRequest request = new IndexRequest("hotel").id(hotelDoc.getId().toString());
    //准备Json文档
    request.source(JSON.toJSONString(hotelDoc),XContentType.JSON);
    //发送请求
    client.index(request,RequestOptions.DEFAULT);

}

查询文档

根据id查询到的文档数据是json,需要反序列化为java对象:

image-20230608160347153

@Test
public void getDocumentByIdTest() throws IOException {
    //准备Request对象
    GetRequest request = new GetRequest("hotel","61083");
    //发送请求
    GetResponse response = client.get(request, RequestOptions.DEFAULT);
    //解析响应
    String json = response.getSourceAsString();

    HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);

    System.out.println(hotelDoc);
}

更新文档

修改文档数据有两种方式:

方式一:全量更新。再次写入id一样的文档,就会删除旧文档,添加新文档

方式二:局部更新。只更新部分字段,我们演示方式二

image-20230608163236154

@Test
public void updateDocumentByIdTest() throws IOException {
    //准备Request对象
    UpdateRequest request = new UpdateRequest("hotel","61083");
    //准备参数,每两个参数为一对key value
    request.doc(
        "price","1000",
        "score","45"
    );

    //发送请求
    client.update(request, RequestOptions.DEFAULT);

}

删除文档

@Test
public void deleteDocumentByIdTest() throws IOException {
    //准备Request对象
    DeleteRequest request = new DeleteRequest("hotel","61083");
    //发送请求
    client.delete(request, RequestOptions.DEFAULT);

}

文档操作的基本步骤:

  1. 初始化RestHighLevelClient
  2. 创建XxxRequest。xxx是Index、Get、Update、Delete
  3. 准备参数(lndex和Update时需要)
  4. 发送请求。调用RestHighLevelClient#client.xxx()方法,xxx是index、get、update、delete
  5. 解析结果(Get时需要)

批量导入文档

@Test
public void bulkRequestTest() throws IOException {
    //批量查询酒店数据
    List<Hotel> hotelList = iHotelService.list();

    //准备Request对象
    BulkRequest request = new BulkRequest();
    //准备参数,添加多个新增的Request
    //转换为文档类型数据
    for (Hotel hotel : hotelList) {
        HotelDoc hotelDoc = new HotelDoc(hotel);
        request.add(new IndexRequest("hotel")
                    .id(hotelDoc.getId().toString())
                    .source(JSON.toJSONString(hotelDoc),XContentType.JSON));
    }
    //发送请求
    client.bulk(request, RequestOptions.DEFAULT);
}

DSL查询语法

DSL Query的分类

Elasticsearch提供了基于JSON的DSL (Domain Specific Language)来定义查询。常见的查询类型包括:

  • 查询所有:查询出所有数据,一般测试用。例如:match_all

  • 全文检索(full text)查询:利用分词器对用户输入内容分词,然后去倒排索引库中匹配。例如

    • match_query

    • multi_match_query

  • 精确查询:根据精确词条值查找数据,一般是查找keyword、数值、日期、boolean等类型字段。例如:

    • ids

    • range

    • term

  • 地理( geo)查询:根据经纬度查询。例如:

    • geo_distance

    • geo_bounding_box

  • 复合(compound)查询:复合查询可以将上述各种查询条件组合起来,合并查询条件。例如:

    • bool

    • function_score

查询的基本语法如下:

image-20230609143602867

全文检索查询

全文检索查询,会对用户输入内容分词,常用于搜索框搜索:

match查询:全文检索查询的一种,会对用户输入内容分词,然后去倒排索引库检索,语法:

image-20230609144035360

# match查询
GET /hotel/_search
{
    "query": {
        "match": {
            "all": "外滩"
        }
    }
}

multi_match: 与match查询类似,只不过允许同时查询多个字段,语法:

image-20230609144337042

# multi_match查询
GET /hotel/_search
{
    "query": {
        "multi_match": {
            "query": "外滩如家",
            "fields": ["name","business","brand"]
        }
    }
}

match和multi_match的区别

  • match:根据一个字段查询
  • multi_match:根据多个字段查询,参与查询字段越多,查询性能越差

建议利用copy to 把多个字段拷贝到一个字段中

精确查询

精确查询一般是查找keyword、数值、日期、boolean等类型字段。所以不会对搜索条件分词。常见的有:

  • term:根据词条精确值查询
  • range:根据值的范围查询

image-20230609145019298

# term查询
GET /hotel/_search
{
    "query": {
        "term": {
            "city": {
                "value": "深圳"
            }
        }
    }
}

image-20230609145305197

# range查询
GET /hotel/_search
{
    "query": {
        "range": {
            "price": {
                "gte": 100,
                "lte": 200
            }
        }
    }
}

地理查询

根据经纬度查询。常见的使用场景包括:

  • 携程:搜索我附近的酒店

  • 滴滴:搜索我附近的出租车

  • 微信:搜索我附近的人

根据经纬度查询,例如∶

geo_bounding_box:查询geo_point值落在某个矩形范围的所有文档

image-20230609145756427

geo_distance:查询到指定中心点小于某个距离值的所有文档

image-20230609145926451

# distance查询
GET /hotel/_search
{
  "query": {
    "geo_distance":{
      "distance":"15km",
      "location":"31.21,121.5"
    }
  }
}

复合查询

复合(compound)查询:复合查询可以将其它简单查询组合起来,实现更复杂的搜索逻辑,例如:

  • fuction score:算分函数查询,可以控制文档相关性算分,控制文档排名。例如百度竞价

当我们利用match查询时,文档结果会根据与搜索词条的关联度打分(_score),返回结果时按照分值降序排列。例如,我们搜索"虹桥如家",结果如下:

image-20230609150559967

es5.0之后使用的是BM25算法

image-20230609150638595

Function Score Query

使用function score query,可以修改文档的相关性算分(query score),根据新得到的算分排序。

image-20230609151100261

案例

给“如家”这个品牌的酒店排名靠前一些

把这个问题翻译一下, function score需要的三要素:

1.哪些文档需要算分加权?

  • 品牌为如家的酒店

2.算分函数是什么?

  • weight就可以

3.加权模式是什么?

  • 求和

image-20230609151242953

GET /hotel/_search
{
  "query": {
    "function_score": {
      "query": {
        "term": {
          "all": {
            "value": "外滩"
          }
        }
      },
      "functions": [
        {
          "filter": {
            "term": {
              "brand": "如家"
            }
          },
          "weight": 10
        }
      ],
      "boost_mode": "multiply"
    }
  }
}

复合查询

Boolean Query布尔查询是一个或多个查询子句的组合。

子查询的组合方式有:

  • must:必须匹配每个子查询,类似“与”
  • should:选择性匹配子查询,类似“或”
  • must_not:必须不匹配,不参与算分,类似“非”.
  • filter:必须匹配,不参与算分

image-20230609152224417

案例

利用bool查询实现功能

需求:搜索名字包含“如家”,价格不高于400,在坐标31.21,121.5周围10km范围内的酒店。

image-20230609152350188

搜索结果处理

排序

elasticsearch支持对搜索结果排序,默认是根据相关度算分(_score)来排序。可以排序字段类型有: keyword类型、数值类型、地理坐标类型、日期类型等。

image-20230609204130671

案例

对酒店数据按照用户评价降序排序,评价相同的按照价格升序排序

评价是score字段,价格是price字段,按照顺序添加两个排序规则即可。

# 排序查询
GET /hotel/_search
{
  "query": {
    "match_all": {}
  },
  "sort": [
    {
      "score": {
        "order": "desc"
      },
      "price": {
        "order": "asc"
      }
    }
  ]
}

实现对酒店数据按照到你的位置坐标的距离升序排序

获取经纬度的方式: https://lbs.amap.com/demo/jsapi-v2/example/map/click-to-get-Inglat/

GET /hotel/_search
{
  "query": {
    "match_all": {}
  },
  "sort": [
    {
      "_geo_distance": {
        "location": {
          "lat": 31,
          "lon": 121
        },
        "order": "asc",
        "unit": "km"
      }
    }
  ]
}

分页

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

elasticsearch中通过修改from、size参数来控制要返回的分页结果:

image-20230609204921598

image-20230609205045916

ES是分布式的,所以会面临深度分页问题。例如按price排序后,获取from = 990,size =10的数据:

  • 1.首先在每个数据分片上都排序并查询前1000条文档。
  • 2.然后将所有节点的结果聚合,在内存中重新排序选出前1000条文档
  • 3.最后从这1000条中,选取从990开始的10条文档

image-20230609205122215

如果搜索页数过深,或者结果集(from + size)越大,对内存和CPU的消耗也越高。因此ES设定结果集查询的上限是10000

深度分页解决方案

针对深度分页,ES提供了两种解决方案

  • search after:分页时需要排序,原理是从上一次的排序值开始,查询下一页数据。官方推荐使用的方式。
  • scroll:原理将排序数据形成快照,保存在内存。官方已经不推荐使用。
总结

from + size:

  • 优点:支持随机翻页
  • 缺点:深度分页问题,默认查询上限( from + size)是10000
  • 场景:百度、京东、谷歌、淘宝这样的随机翻页搜索

after search:

  • 优点:没有查询上限(单次查询的size不超过10000)
  • 缺点:只能向后逐页查询,不支持随机翻页
  • 场景:没有随机翻页需求的搜索,例如手机向下滚动翻页

scroll:

  • 优点:没有查询上限(单次查询的size不超过10000)
  • 缺点:会有额外内存消耗,并且搜索结果是非实时的
  • 场景:海量数据的获取和迁移。从ES7.1开始不推荐,建议用aftersearch方案。

高亮

就是在搜索结果中把搜索关键字突出显示。

原理是这样的:

  • 将搜索结果中的关键字用标签标记出来
  • 在页面中给标签添加css样式

语法:

image-20230609205925771

默认情况下,ES搜索字段必须与高亮字段一致

ES默认就是给结果加的<em></em>标签,可以省略不写

GET /hotel/_search
{
  "query": {
    "match": {
      "all":"如家"
    }
  },
  "highlight": {
    "fields": {
      "name": {
        "require_field_match": "false"
      }
    }
  }
}

RestClient查询文档

match_all

我们通过match_all来演示下基本的API,先看请求DSL的组织:

image-20230611105708648

对响应结果的解析

image-20230611111334722

@Test
public void testMatchAll() throws IOException {
    //准备request
    SearchRequest request = new SearchRequest("hotel");
    //准备DSL
    request.source().query(QueryBuilders.matchAllQuery());
    //发送请求
    SearchResponse response = client.search(request, RequestOptions.DEFAULT);
    //解析响应
    SearchHits searchHits = response.getHits();
    //获取总条数
    long total = searchHits.getTotalHits().value;
    System.out.println("共搜索到"+total+"条数据");
    //获取文档数组
    SearchHit[] hits = searchHits.getHits();
    //遍历文档数组,就是每条数据
    for (SearchHit hit : hits) {
        //获取文档source
        String json = hit.getSourceAsString();
        //反序列化
        HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);
        System.out.println(hotelDoc);
    }
}

全文检索查询

全文检索的match和multi_match查询与match_all的API基本一致。差别是查询条件,也就是query的部分。

@Test
public void testMatch() throws IOException {
    //准备request
    SearchRequest request = new SearchRequest("hotel");
    //准备DSL
    request.source().query(QueryBuilders.matchQuery("all","如家"));
    //发送请求
    SearchResponse response = client.search(request, RequestOptions.DEFAULT);
    //解析响应
    SearchHits searchHits = response.getHits();
    //获取总条数
    long total = searchHits.getTotalHits().value;
    System.out.println("共搜索到"+total+"条数据");
    //获取文档数组
    SearchHit[] hits = searchHits.getHits();
    //遍历文档数组,就是每条数据
    for (SearchHit hit : hits) {
        //获取文档source
        String json = hit.getSourceAsString();
        //反序列化
        HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);
        System.out.println(hotelDoc);
    }
}

精确查询

精确查询常见的有term查询和range查询,同样利用QueryBuilders实现:

@Test
public void testTerm() throws IOException {
    //准备request
    SearchRequest request = new SearchRequest("hotel");
    //准备DSL
    request.source().query(QueryBuilders.termQuery("city","深圳"));
    //发送请求
    SearchResponse response = client.search(request, RequestOptions.DEFAULT);
    //解析响应
    responseHandle(response);
}

@Test
public void testRange() throws IOException {
    //准备request
    SearchRequest request = new SearchRequest("hotel");
    //准备DSL
    request.source().query(QueryBuilders.rangeQuery("price").lt(250));
    //发送请求
    SearchResponse response = client.search(request, RequestOptions.DEFAULT);
    //解析响应
    responseHandle(response);
}

复合查询-boolean query

精确查询常见的有term查询和range查询,同样利用QueryBuilders实现:

@Test
public void testBool() throws IOException {
    //准备request
    SearchRequest request = new SearchRequest("hotel");
    //准备DSL
    BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
    boolQuery.must(QueryBuilders.termQuery("city","上海"));
    boolQuery.filter(QueryBuilders.rangeQuery("price").lte(200));
    request.source().query(boolQuery);
    //发送请求
    SearchResponse response = client.search(request, RequestOptions.DEFAULT);
    //解析响应 
    responseHandle(response);
}

组合查询-function score

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-56BqKf2J-1687077051212)(…/…/…/AppData/Roaming/Typora/typora-user-images/image-20230611142703876.png)]

//算分控制
FunctionScoreQueryBuilder functionScoreQuery = QueryBuilders.functionScoreQuery(
    //原始查询,相关性算分的查询
    boolQuery
    //function score的数组
    , new FunctionScoreQueryBuilder.FilterFunctionBuilder[]{
        // 其中的一个function score元素
        new FunctionScoreQueryBuilder.FilterFunctionBuilder(
            //过滤条件
            QueryBuilders.termQuery("isAD",true)
            //算法函数
            , ScoreFunctionBuilders.weightFactorFunction(10)
        )
    });

排序和分页

搜索结果的排序和分页是与query同级的参数,对应的API如下:

image-20230611114509441

image-20230611140419444

@Test
public void testPageAndSort() throws IOException {
    //准备request
    SearchRequest request = new SearchRequest("hotel");
    //准备DSL 
    request.source().query(QueryBuilders.matchAllQuery());
    request.source().from(0).size(5);
    request.source().sort("price", SortOrder.ASC);
    //发送请求
    SearchResponse response = client.search(request, RequestOptions.DEFAULT);
    //解析响应
    responseHandle(response);
}

高亮

高亮API包括请求DSL构建和结果解析两部分。我们先看请求的DSL构建:

image-20230611125338079

@Test
public void testHighLight() throws IOException {
    //准备request
    SearchRequest request = new SearchRequest("hotel");
    //准备DSL
    request.source().query(QueryBuilders.matchQuery("city","深圳"));

    request.source().highlighter(new HighlightBuilder()
                                 .field("name")
                                 .requireFieldMatch(false)
                                );
    //发送请求
    SearchResponse response = client.search(request, RequestOptions.DEFAULT);
    //解析响应
    responseHandle(response);
}

结果解析处理

private void responseHandle(SearchResponse response){
    //解析响应
    SearchHits searchHits = response.getHits();
    //获取总条数
    long total = searchHits.getTotalHits().value;
    System.out.println("共搜索到"+total+"条数据");
    //获取文档数组
    SearchHit[] hits = searchHits.getHits();
    //遍历文档数组,就是每条数据
    for (SearchHit hit : hits) {
        //获取文档source
        String json = hit.getSourceAsString();
        //反序列化
        HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);
        //获取高亮结果
        Map<String, HighlightField> highlightFields = hit.getHighlightFields();
        if (!CollectionUtils.isEmpty(highlightFields)){
            //根据字段名获取高亮结果
            HighlightField highlightField = highlightFields.get("name");
            if (highlightField != null){
                String name = highlightField.getFragments()[0].string();
                //覆盖非高亮结果
                hotelDoc.setName(name);
            }
        }
        System.out.println(hotelDoc);
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值