【ElasticSearch】使用 Java 客户端 RestClient 实现对文档的查询操作,以及对搜索结果的排序、分页、高亮处理

news2024/11/18 22:42:23

文章目录

  • 前言:RestClient 查询文档的 RestAPI
  • 一、全文检索查询
    • 1.1 match_all 查询
    • 1.2 match 查询
    • 1.3 multi_match 查询
  • 二、精确查询
    • 2.1 term 查询
    • 2.2 range 查询
  • 三、复合查询:Boolean 查询与 function score 查询的综合案例
  • 四、对查询结果的处理
    • 4.1 将查询结果按照自己的距离远近排序
    • 4.2 根据前端请求参数进行分页操作
    • 4.3 对搜索关键字进行高亮处理


前言:RestClient 查询文档的 RestAPI

在 Elasticsearch 中,通过 RestAPI 进行 DSL 查询语句的构建通常是通过 HighLevelRestClient 中的 resource() 方法来实现的。该方法包含了查询、排序、分页、高亮等所有功能,为构建复杂的查询提供了便捷的接口。

RestAPI示意图

RestAPI 中构建查询条件的核心部分是由一个名为 QueryBuilders 的工具类提供的。该工具类包含了各种查询方法,如下图所示:

QueryBuilders工具类

查询的基本步骤如下:

  1. 创建 SearchRequest 对象。
  2. 准备 Request.source(),也就是 DSL。
  3. 使用 QueryBuilders 构建查询条件。
  4. 将查询条件传入 Request.source().query() 方法。
  5. 发送请求,得到结果。
  6. 解析结果,可以参考 JSON 结果,从外到内逐层解析。

这种方式使得构建复杂的查询变得简单而灵活。在接下来的实例中,我们将深入学习如何使用 HighLevelRestClient 中的 resource() 方法构建各种查询,并充分利用 QueryBuilders 工具类来满足不同的搜索需求。

一、全文检索查询

1.1 match_all 查询

match_all 查询的单元测试代码:

@Test
void testMatchAll() throws IOException {
    // 1. 准备Request
    SearchRequest request = new SearchRequest("hotel");

    // 2. 组织 DSL 请求
    request.source().query(QueryBuilders.matchAllQuery());

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

    // 4. 解析结果
    List<HotelDoc> hotelDocs = handleResponse(response);
    System.out.println(hotelDocs);
}

这个测试代码演示了如何使用 match_all 查询来获取指定索引(这里是 “hotel”)下的所有文档。具体步骤如下:

  1. 创建 SearchRequest 对象,指定索引为 “hotel”。
  2. 使用 QueryBuilders.matchAllQuery() 构建查询条件。
  3. 将查询条件添加到请求的 DSL 中。
  4. 发送请求,得到查询结果。
  5. 解析查询结果,将文档转换为 HotelDoc 对象。

1.2 match 查询

@Test
void testMatch() throws IOException {
    // 1. 准备Request
    SearchRequest request = new SearchRequest("hotel");

    // 2. 组织 DSL 请求
    request.source().query(QueryBuilders.matchQuery("all", "如家"));

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

    // 4. 解析结果
    List<HotelDoc> hotelDocs = handleResponse(response);
    System.out.println(hotelDocs);
}

这个测试代码演示了如何使用 match 查询来搜索包含特定关键词(这里是 “如家”)的文档。具体步骤如下:

  1. 创建 SearchRequest 对象,指定索引为 “hotel”。
  2. 使用 QueryBuilders.matchQuery("all", "如家") 构建查询条件,表示在 “all” 字段中搜索包含 “如家” 的文档。
  3. 将查询条件添加到请求的 DSL 中。
  4. 发送请求,得到查询结果。
  5. 解析查询结果,将文档转换为 HotelDoc 对象。

1.3 multi_match 查询

@Test
void testMultiMatch() throws IOException {
    // 1. 准备Request
    SearchRequest request = new SearchRequest("hotel");

    // 2. 组织 DSL 请求
    request.source().query(QueryBuilders.multiMatchQuery("如家", "name", "business"));

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

    // 4. 解析结果
    List<HotelDoc> hotelDocs = handleResponse(response);
    System.out.println(hotelDocs);
}

这个测试代码演示了如何使用 multi_match 查询来在多个字段(这里是 “name” 和 “business”)中搜索包含特定关键词(这里是 “如家”)的文档。具体步骤如下:

  1. 创建 SearchRequest 对象,指定索引为 “hotel”。
  2. 使用 QueryBuilders.multiMatchQuery("如家", "name", "business") 构建查询条件,表示在 “name” 和 “business” 字段中搜索包含 “如家” 的文档。
  3. 将查询条件添加到请求的 DSL 中。
  4. 发送请求,得到查询结果。
  5. 解析查询结果,将文档转换为 HotelDoc 对象。

可以看到,这些测试代码的结构类似,只是在构建查询条件时使用了不同的 QueryBuilders 方法,用于满足不同的查询需求。

二、精确查询

精确查询常见的有 term 查询和 range 查询,同样使用 QueryBuilders 指定具体的查询方式。

2.1 term 查询

@Test
void testTerm() throws IOException {
    // 1. 准备Request
    SearchRequest request = new SearchRequest("hotel");

    // 2. 组织 DSL 请求
  	request.source().query(QueryBuilders.termQuery("city", "上海"));

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

    // 4. 解析结果
    List<HotelDoc> hotelDocs = handleResponse(response);
    System.out.println(hotelDocs);
}

这个测试代码演示了如何使用 term 查询来搜索指定字段(这里是 “city”)中包含特定关键词(这里是 “上海”)的文档。具体步骤如下:

  1. 创建 SearchRequest 对象,指定索引为 “hotel”。
  2. 使用 QueryBuilders.termQuery("city", "上海") 构建查询条件,表示在 “city” 字段中搜索包含 “上海” 的文档。
  3. 将查询条件添加到请求的 DSL 中。
  4. 发送请求,得到查询结果。
  5. 解析查询结果,将文档转换为 HotelDoc 对象。

2.2 range 查询

@Test
void testRange() throws IOException {
    // 1. 准备Request
    SearchRequest request = new SearchRequest("hotel");

    // 2. 组织 DSL 请求
    request.source().query(QueryBuilders.rangeQuery("price").gte(150).lte(200));

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

    // 4. 解析结果
    List<HotelDoc> hotelDocs = handleResponse(response);
    System.out.println(hotelDocs);
}

这个测试代码演示了如何使用 range 查询来搜索指定字段(这里是 “price”)中在给定范围内的文档。具体步骤如下:

  1. 创建 SearchRequest 对象,指定索引为 “hotel”。
  2. 使用 QueryBuilders.rangeQuery("price").gte(150).lte(200) 构建查询条件,表示在 “price” 字段中搜索价格在 150 到 200 之间的文档。
  3. 将查询条件添加到请求的 DSL 中。
  4. 发送请求,得到查询结果。
  5. 解析查询结果,将文档转换为 HotelDoc 对象。

这些测试代码演示了如何使用精确查询来满足特定的搜索需求。在实际应用中,可以根据具体的业务场景和数据结构选择不同的查询方式。

三、复合查询:Boolean 查询与 function score 查询的综合案例

例如,现在通过一个酒店预订网址的查询功能不但可以在搜索框输入关键字进行查询,还可以勾选指定的筛选条件,比如城市、星级、品牌和价格范围:


另外,再所有的酒店数据中还存在一部分属于广告(ES 文档中新增一个布尔类型的 isAD 字段表示),要求查询结果中的广告需要顶置显示,因此整个查询分为两部分:即 boolean 查询和 function score 查询。

  • boolean 查询:实现对搜索关键字的查询,以及过滤:城市、星级、品牌和价格等条件;
  • function score 查询:实现对广告文档的相关性增加操作。

具体的查询代码如下:

private static void buildBasicQuery(RequestParams params, SearchRequest request) {
    // 1. 原始查询 Query

    BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
    // 关键字搜索 must
    String key = params.getKey();
    if (key == null || key.isEmpty()) {
        boolQuery.must(QueryBuilders.matchAllQuery());
    } else {
        boolQuery.must(QueryBuilders.matchQuery("all", key));
    }

    // 城市条件
    if (params.getCity() != null && !params.getCity().isEmpty()) {
        boolQuery.filter(QueryBuilders.termQuery("city", params.getCity()));
    }
    // 品牌条件
    if (params.getBrand() != null && !params.getBrand().isEmpty()) {
        boolQuery.filter(QueryBuilders.termQuery("brand", params.getBrand()));
    }
    // 星级条件
    if (params.getStarName() != null && !params.getStarName().isEmpty()) {
        boolQuery.filter(QueryBuilders.termQuery("starName", params.getStarName()));
    }
    // 价格范围
    if (params.getMaxPrice() != null && params.getMinPrice() != null) {
        boolQuery.filter(QueryBuilders
                .rangeQuery("price").lte(params.getMaxPrice()).gte(params.getMinPrice()));
    }

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

    request.source().query(functionScoreQuery);
}

对上述代码的详细说明:

  • RequestParams 是一个封装了前端请求参数的对象,里面包含了 ES 文档中的各个字段;
  • 在这个方法中,首先构建了一个 boolQuery 作为原始查询条件,包含了关键字搜索、城市、品牌、星级和价格范围等多个条件;
  • 接着,构建了一个 functionScoreQuery,将原始查询作为参数传入,同时定义了一个 FilterFunctionBuilder,用于处理广告部分的查询,对广告文档的分数进行加权,使其在查询结果中更靠前显示;
  • 最后,将 functionScoreQuery 设置为请求的查询条件。

这个综合的查询案例涵盖了多个条件的组合查询以及对特定文档的加权分数处理。在实际应用中,可以根据业务需求扩展和修改这个查询方法。

四、对查询结果的处理

4.1 将查询结果按照自己的距离远近排序

在前端查询酒店数据的时候,一般都会定位获取到自己当前的位置,然后传递给后端。

  • RequestParams 作为前端参数的封装对象,包含了这个位置信息,可通过get 方法进行获取;
  • 然后可以通过 SortBuilders 中的 geoDistanceSort 方法计算距离并进行排序操作,即可获取酒店与自己的实际距离。
String location = params.getLocation();
if (location != null && !location.isEmpty()) {
    request.source().sort(SortBuilders
            .geoDistanceSort("location", new GeoPoint(location))
            .order(SortOrder.ASC)
            .unit(DistanceUnit.KILOMETERS)
    );
}

4.2 根据前端请求参数进行分页操作

分页需要的页码以及页面大小一般都由前端传递参数给后端,然后后端获取这两个参数进行分页操作。

  • RequestParams 作为前端参数的封装对象,包含了这两个参数,可通过get 方法进行获取;
  • 然后再根据分页偏移量的计算公式offset = (page - 1) * size 即可获取偏移量,然后通过 sourcefromsize 方法,即可实现分页查询。

例如:

int page = params.getPage();
            int size = params.getSize();
            request.source().from((page - 1) * size).size(size);

4.3 对搜索关键字进行高亮处理

要对搜索关键字进行高亮处理同样非常简单,只需要使用 resouce 中的 highlighter 方法指定要进行高亮的字段即可。

实现的示例代码如下:

@Test
void testHighlight() throws IOException {

    // 1. 准备Request
    SearchRequest request = new SearchRequest("hotel");

    // 2. 组织 DSL 请求
    // 2.1 query
    request.source().query(QueryBuilders.matchQuery("all", "如家"));

    // 2.2 设置高亮
    request.source().highlighter(new HighlightBuilder().field("name").requireFieldMatch(false));

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

    // 4. 解析结果
    List<HotelDoc> hotelDocs = handleResponse(response);
    System.out.println(hotelDocs);
}

说明:

注意requireFieldMatch方法的作用是高亮的字段是否需要和搜索的字段匹配。例如:使用字段 all 查询匹配 brand 字段,此时 brand 字段使用了 copy_toall 字段,因此要高亮的字段并不匹配,如果不设置 requireFieldMatchfalse 则会高亮失败。

对结果的解析:
实现高亮的原理是对搜索关键字加上了 <em> </em> 标签,通过在 Kibana 中使用高亮处理的 DSL 语句可以发现:


其实高亮的处理并没有作用到查询出的原始文档中,而是在每个 hits 里面新增了一个 highlight 字段,里面包含了高亮的关键字,因此还需要对 handleResponse 函数进行改造,使得能够处理高亮的查询结果:

例如,在handleResponse 函数的 result.add(hotelDoc) 这句代码之前,新增了以下代码用于处理高亮的情况,即使用高亮处理了的关键字替换查询结果中对应的关键字:

// 获取高亮结果
Map<String, HighlightField> highlightFields = hit.getHighlightFields();
if(highlightFields != null && !highlightFields.isEmpty()) {
    // 获取高亮名称
    HighlightField highlightField = highlightFields.get("name");
    // 获取高亮的值
    String name = highlightField.getFragments()[0].toString();
    hotelDoc.setName(name);
}

当运行这段测试代码,即可发现成功对 name 字段的 “如家” 添加了 <em> </em> 标签:

在这里插入图片描述

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

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

相关文章

关于Qualifier你要知道的二三事

&#x1f35e; Qualifier注解的作用-定义Bean-指定Bean的名称 Qualifier注解可以区分具有相同类型的多个Bean&#xff0c;用于明确指定要注入的Bean的名称或限定符。通过为要注入的Bean添加 Qualifier注解&#xff0c;你可以告诉Spring应该使用哪个Bean&#xff0c;以解决Spri…

黑马JVM总结(三十一)

&#xff08;1&#xff09;类加载器-概述 启动类加载器-扩展类类加载器-应用程序类加载器 双亲委派模式&#xff1a; 类加载器&#xff0c;加载类的顺序是先依次请问父级有没有加载&#xff0c;没有加载自己才加载&#xff0c;扩展类加载器在getParent的时候为null 以为Boots…

zabbix监控实战1

1、zabbix监控平台部署 重新克隆纯净虚拟机 数据库初始化 修改密码为WHqwerty123 初始化完成 创建zabbix数据库 基础配置和服务启动 访问 2、zabbix添加监控节点 修改字体文件 在客户端 手动添加监控节点 自动添加监控节点 3、zabbix api 自动注册 停掉自动发现 删掉serve…

VMware 下模拟软 RAID 的创建及故障恢复

Author&#xff1a;rab 目录 前言一、创建 RAID1.1 环境1.2 什么是 RAID&#xff1f;1.3 软 RAID 和硬 RAID1.4 如何创建软 RAID&#xff1f; 二、故障模拟与数据恢复2.1 故障模拟2.2 故障恢复 思考&#xff1f; 前言 一块物理硬盘要投入生产使用&#xff0c;一般会经历一下这…

面试经典 150 题 2 —(滑动窗口)— 3. 无重复字符的最长子串

3. 无重复字符的最长子串 方法 class Solution { public:int lengthOfLongestSubstring(string s) {int result 0, length s.length();int start 0, end 0;while(end < length){// 发现有重复字符时&#xff0c;可以直接把左指针移动到第一个重复字符的下一个位置for(i…

Web前端-Vue2+Vue3基础入门到实战项目-Day3(生命周期, 案例-小黑记账清单, 工程化开发入门)

Web前端-Vue2Vue3基础入门到实战项目-Day3 生命周期生命周期 & 生命周期四个阶段生命周期钩子生命周期案例created应用mounted应用 案例 - 小黑记账清单工程化开发入门工程化开发和脚手架项目运行流程index.htmlmain.js 组件化组件注册局部注册全局注册 来源 生命周期 生命…

1312. 序列统计

1312. 序列统计 - AcWing题库 L~R范围可以等同于0~R-L范围 相当于在R-L1个数中选出k个数 令 则变为 相当于在R-Lk个数中选出k个数 需要计算 #include<bits/stdc.h> #define IOS ios::sync_with_stdio(0);cin.tie(0);cout.tie(0); #define endl \nusing namespace std;t…

Filter(过滤器)Intercerptor(拦截器)

Filter过滤器 顾名思义&#xff0c;Filter可以对请求进行过滤&#xff0c;当浏览器发送请求时&#xff0c;首先先会被Filter进行拦截&#xff0c;Filter可以决定此次拦截是否放行&#xff0c;如果选择放行&#xff0c;放行之后还会返回Filter执行剩下的代码。 使用方法&…

YOLOv7独家改进:Multi-Dconv Head Transposed Attention注意力,效果优于MHSA| CVPR2022

💡💡💡本文独家改进:Multi-Dconv Head Transposed Attention注意力,可以高效的进行高分辨率图像处理,从而提升检测精度 MDTA | 亲测在多个数据集能够实现大幅涨点 收录: YOLOv7高阶自研专栏介绍: http://t.csdnimg.cn/tYI0c ✨✨✨前沿最新计算机顶会复现 �…

初识Java 13-2 异常

目录 标准Java异常 新特性&#xff1a;更好的NullPointerException报告机制 使用finally执行清理 finally有什么用 在return时使用finally 缺陷&#xff1a;异常丢失 异常的约束 构造器 本笔记参考自&#xff1a; 《On Java 中文版》 标准Java异常 Throwable类描述了任…

项目生命周期

阶段 项目经理或组织可以将每一个项目划分为若干个阶段&#xff0c;以便于有效地进行管理控制&#xff0c;并实施该项目组织的日常运作联系起来。 项目划分为四个阶段&#xff1a;概念、计划、实施、结束 生命期 项目阶段合在一起称为项目生命期&#xff0c;项目生命期确定了将…

Go流程控制与快乐路径原则

Go流程控制与快乐路径原则 文章目录 Go流程控制与快乐路径原则一、流程控制基本介绍二、if 语句2.1 if 语句介绍2.2 单分支结构的 if 语句形式2.3 Go 的 if 语句的特点2.3.1 分支代码块左大括号与if同行2.3.2 条件表达式不需要括号 三、操作符3.1 逻辑操作符3.2 操作符的优先级…

【k8s】ingress-nginx通过header路由到不同后端

K8S中ingress-nginx通过header路由到不同后端 背景 公司使用ingress-nginx作为网关的项目&#xff0c;需要在相同域名、uri&#xff0c;根据header将请求转发到不同的后端中在稳定发布的情况下&#xff0c;ingress-nginx是没有语法直接支持根据header做转发的。但是这个可以利…

ubuntu配置yolov5环境

本文硬件平台为工控机&#xff0c;系统环境为ubuntu18 配置yolov5步骤 1.下载pytorch和torchvision软件包 由于在线安装容易出现安装失败&#xff0c;所以本文使用的是本地安装。本文是基于miniconda安装的&#xff0c;miniconda安装参考之前的博客&#xff1a;ubuntu中安装m…

ssm+vue的台球厅管理系统(有报告)。Javaee项目,ssm vue前后端分离项目。

演示视频&#xff1a; ssmvue的台球厅管理系统(有报告)。Javaee项目&#xff0c;ssm vue前后端分离项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层体系结构&#xff0c;通过Spring S…

差模电感和共模电感的差别

一、初步了解差模、共模的概念 超链接&#xff0c;点击鼠标打开&#xff1a;X电容和Y电容&#xff1b;差模与共模初认识 二、差模和共模电感的二者区别 共模电感和差模电感&#xff0c;是电路中常用的滤波电感、EMI器件&#xff0c;两者经常以环形电感线圈的方式存在。 首先…

理解http中cookie!C/C++实现网络的HTTP cookie

HTTP嗅探&#xff08;HTTP Sniffing&#xff09;是一种网络监控技术&#xff0c;通过截获并分析网络上传输的HTTP数据包来获取敏感信息或进行攻击。其中&#xff0c;嗅探器&#xff08;Sniffer&#xff09;是一种用于嗅探HTTP流量的工具。 在HTTP嗅探中&#xff0c;cookie是一…

Python一步到位实现图像转PDF自动化处理详解

什么是 img2pdf 库&#xff1f; img2pdf 是一个 Python 库&#xff0c;它可以让你轻松地把多张图像转换为 PDF 文件。它支持多种图像格式&#xff0c;如 JPG, PNG, GIF, BMP 等&#xff0c;并且可以自动调整图像的大小和方向&#xff0c;以适应 PDF 的页面大小和方向。它还可以…

跨域问题-笔记

这里写目录标题 一、什么是跨域&#xff1a;二、跨域问题解决思路&#xff1a;1.从浏览器入手2.从域名入手3.从jsonp入手4.从代理入手 一、什么是跨域&#xff1a; 跨域指的是不同服务器之间不能相互访问各自的资源或者数据&#xff0c;这出于一个策略——“同源策略”&#x…

【力扣】2578. 最小和分割

【力扣】2578. 最小和分割 文章目录 【力扣】2578. 最小和分割1. 题目介绍2. 思路3. 解题代码4. 疑问&#xff1f;5. Danger参考 1. 题目介绍 给你一个正整数 num &#xff0c;请你将它分割成两个非负整数 num1 和 num2 &#xff0c;满足&#xff1a; num1 和 num2 直接连起来…