SpringBoot中间件ElasticSearch

news2025/5/20 4:08:59
        Elasticsearch是一个基于 Lucene 的搜索服务器。它提供了一个分布式多用户能力的 全文搜索引擎 ,基于RESTful web 接口。 Elasticsearch 是用 Java 语言开发的,并作为 Apache 许可条款下的开放源码发布,是一种流行的企业级搜索引擎。Elasticsearch 用于 云计算 中,能够达到实时搜索,稳定,可靠,快速,安装使用方便。官方客户端在Java .NET C# )、 PHP Python Apache Groovy Ruby 和许多其他语言中都是可用的。根据DB-Engines 的排名显示, Elasticsearch 是最受欢迎的企业搜索引擎,其次是 Apache Solr ,也是基于 Lucene。
        Lucene是一个 Java 语言的搜索引擎类库 , Apache 公司的顶级项目,由 DougCutting 1999 年研发。
官网地址: https:// lucene.apache.org/

 重要特征:

  1. 分布式的实时文件存储,每个字段都被索引并可被搜索
  2. 实时分析的分布式搜索引擎
  3. 可以扩展到上百台服务器,处理PB级结构化或非结构化数据

1. 倒排索引概述

倒排索引的概念是基于 MySQL 这样的正向索引而言的。

1.1 正向索引

那么什么是正向索引呢?例如给下表( tb_goods )中的 id 创建索引:

如果是根据id查询,那么直接走索引,查询速度非常快。 

但如果是基于title做模糊查询,只能是逐行扫描数据,流程如下: 

  1. 用户搜索数据,条件是title符合 "%手机%"  
  2. 逐行获取数据,比如id1的数据 
  3. 判断数据中的title是否符合用户搜索条件 
  4. 如果符合则放入结果集,不符合则丢弃。回到步骤1  
逐行扫描,也就是全表扫描,随着数据量增加,其查询效率也会越来越低。当数据量达到数百万时,就是一场灾难。

 1.2 倒排索引

倒排索引中有两个非常重要的概念:
  • 文档( Document :用来搜索的数据,其中的每一条数据就是一个文档。例如一个网页、一个商品信息;
  • 词条( Term :对文档数据或用户搜索数据,利用某种算法分词,得到的具备含义的词语就是词 条。例如:我是中国人,就可以分为:我、是、中国人、中国、国人这样的几个词条;

创建倒排索引是对正向索引的一种特殊处理,流程如下:

  • 将每一个文档的数据利用算法分词,得到一个个词条
  • 创建表,每行数据包括词条、词条所在文档id、位置等信息
  • 因为词条唯一性,可以给词条创建索引,例如hash表结构索引

 如图:

倒排索引的搜索流程如下(以搜索"华为手机"为例):

  1. 用户输入条件 "华为手机" 进行搜索。
  2. 对用户输入内容分词,得到词条: 华为 手机
  3. 拿着词条在倒排索引中查找,可以得到包含词条的文档id123
  4. 拿着文档id到正向索引中查找具体文档。

如图:

虽然要先查询倒排索引,再查询倒排索引,但是无论是词条、还是文档 id 都建立了索引,查询速度非常快!无需全表扫描。

 1.3 正向和倒排

那么为什么一个叫做正向索引,一个叫做倒排索引呢?
  • 正向索引是最传统的,根据id索引的方式。但根据词条查询时,必须先逐条获取每个文档,然后判断文档中是否包含所需要的词条,是根据文档找词条的过程
  • 倒排索引则相反,是先找到用户要搜索的词条,根据词条得到保护词条的文档的id,然后根据id 获取文档。是根据词条找文档的过程

正向索引:

  • 优点:
    • 可以给多个字段创建索引
    • 根据索引字段搜索、排序速度非常快
  •  缺点:
    • 根据非索引字段,或者索引字段中的部分词条查找时,只能全表扫描。
倒排索引:
  • 优点:
    • 根据词条搜索、模糊搜索时,速度非常快
  • 缺点:
    • 只能给词条创建索引,而不是字段
    • 无法根据字段做排序

2. es的一些概念

elasticsearch 中有很多独有的概念,与 mysql 中略有差别,但也有相似之处。

2.1 文档与字段

elasticsearch 是面向 文档( Document 存储的,可以是数据库中的一条商品数据,一个订单信息。文档数据会被序列化为json 格式后存储在 elasticsearch 中:

Json文档中往往包含很多的字段(Field,类似于数据库中的列。

2.2 索引和映射 

索引( Index ):    就是相同类型的文档的集合。
例如:
  • 所有用户文档,就可以组织在一起,称为用户的索引;
  • 所有商品的文档,可以组织在一起,称为商品的索引;
  • 所有订单的文档,可以组织在一起,称为订单的索引;

 因此,我们可以把索引当做是数据库中的表。

数据库的表会有约束信息,用来定义表的结构、字段的名称、类型等信息。因此,索引库中就有 映射 mapping ,是索引中文档的字段约束信息,类似表的结构约束。

2.3 MySQL 与 elasticsearch

我们统一的把 mysql elasticsearch 的概念做一下对比:
MySQLElasticsearch说明
TableIndex
索引 (index) ,就是文档的集合,类似数据库的表 (table)
Row
Document
文档( Document ),就是一条条的数据,类似数据库中的行(Row ),文档都是 JSON 格式
Column
Field
字段( Field ),就是 JSON 文档中的字段,类似数据库中的列(Column
Schema
Mapping
Mapping (映射)是索引中文档的约束,例如字段类型约束。类似数据库的表结构(Schema
SQL
DSL
DSL elasticsearch 提供的 JSON 风格的请求语句,用来操作elasticsearch,实现 CRUD

两者各有自己的擅长支出:

  • Mysql:擅长事务类型操作,可以确保数据的安全和一致性
  • Elasticsearch:擅长海量数据的搜索、分析、计算

因此在企业中,往往是两者结合使用:

  • 对安全性要求较高的写操作,使用mysql实现
  • 对查询性能要求较高的搜索需求,使用elasticsearch实现
  • 两者再基于某种方式,实现数据的同步,保证一致性

2.4 环境安装 

Windows ES 下载:https://www.elastic.co/cn/downloads/elasticsearch
Windows ES 安装与启动:
  • 运行 elasticsearch.bat
  • 访问localhost:9200能看到json代表启动成功

2.5 IK分词器 

分词器的作用是什么?
  • 创建倒排索引时对文档分词
  • 用户搜索时,对输入的内容分词

 IK分词器有几种模式?

  • ik_smart:智能切分,粗粒度
  • ik_max_word:最细切分,细粒度

安装

下载:https://github.com/medcl/elasticsearch-analysis-ik/releases

  • ES安装目录下找到plugins目录创建ik文件夹
  • ik分词器解压缩在此目录并重启ES即可

2.6 索引库操作

索引库就类似数据库表, mapping 映射就类似表的结构。
我们要向 es 中存储数据,必须先创建

2.6.1 mapper映射属性

mapping 是对索引库中文档的约束,常见的 mapping 属性包括:
  • type:字段数据类型,常见的简单类型有:
    • 字符串:text(可分词的文本)、keyword(精确值,例如:品牌、国家、ip地址)
    • 数值:longintegershortbytedoubleflfloat
    • 布尔:boolean
    • 日期:date
    • 对象:object

  • index:是否创建索引,默认为true
  • analyzer:使用哪种分词器
  • properties:该字段的子字段
例如下面的 json 文档:
{
    "age": 18,
    "weight": 70.2,
    "isMarried": false,
    "info": "apesourceJavaEE王讲师",
    "email": "wangls@163.com",
    "score": [99.1, 99.5, 98.9],
    "name": {
        "firstName": "师傅",
        "lastName": "王"
    }
}
对应的每个字段映射( mapping ):
  • age:类型为 integer;参与搜索,因此需要indextrue;无需分词器
  • weight:类型为flfloat;参与搜索,因此需要indextrue;无需分词器
  • isMarried:类型为boolean;参与搜索,因此需要indextrue;无需分词器
  • info:类型为字符串,需要分词,因此是text;参与搜索,因此需要indextrue;分词器可以用 ik_smart
  • email:类型为字符串,但是不需要分词,因此是keyword;不参与搜索,因此需要index为false;无需分词器
  • score:虽然是数组,但是我们只看元素的类型,类型为flfloat;参与搜索,因此需要index为true;无需分词器
  • name:类型为object,需要定义多个子属性
    • name.fifirstName;类型为字符串,但是不需要分词,因此是keyword;参与搜索,因此需要 index为true;无需分词器
    • name.lastName;类型为字符串,但是不需要分词,因此是keyword;参与搜索,因此需要 index为true;无需分词器

2.6.2 创建索引库和映射 

基本语法:

  • 请求方式:PUT
  • 请求路径:/索引库名,可以自定义
  • 请求参数:mapping映射

格式:

PUT /索引库名称
{
    "mappings": {
        "properties": {
            "字段名":{
                "type": "text",
                "analyzer": "ik_smart"
            },
            "字段名2":{
                "type": "keyword",
                "index": "false"
            },
            "字段名3":{
                "properties": {
                    "子字段": {
                        "type": "keyword"
                    }
                }
            },
            // ...略
        }
    }
}

 2.6.3 查询索引库

基本语法: 

  • 请求方式:GET
  • 请求路径:/索引库名
  • 请求参数:无

格式:GET /索引库名

 2.6.3 修改索引库

        倒排索引结构虽然不复杂,但是一旦数据结构改变(比如改变了分词器),就需要重新创建倒排索引,这简直是灾难。因此索引库一旦创建,无法修改mapping
        虽然无法修改mapping 中已有的字段,但是却允许添加新的字段到 mapping 中,因为不会对倒排索引产生影响。
语法:
PUT /索引库名/_mapping
{
    "properties": {
        "新字段名":{
            "type": "integer"
        }
    }
}

2.6.4 删除索引库

语法:

  • 请求方式:DELETE
  • 请求路径:/索引库名
  • 请求参数:无

格式DELETE /索引库名

2.6.5 总结

  • 创建索引库:PUT /索引库名
  • 查询索引库:GET /索引库名
  • 删除索引库:DELETE /索引库名
  • 添加字段:PUT /索引库名/_mapping

2.7 文档操作

2.7.1 新增文档

语法:

POST /索引库名/_doc/文档id
{
    "字段1": "值1",
    "字段2": "值2",
    "字段3": {
        "子属性1": "值3",
        "子属性2": "值4"
    },
    // ...
}

响应:result:created

2.7.2 查询文档

根据 rest 风格,新增是 post ,查询应该是 get ,不过查询一般都需要条件,这里我们把文档 id 带上。
语法: GET / { 索引库名称 } / _doc / { id }
查看数据: GET / { 索引库名称 } / _doc / 1

2.7.3 删除文档

删除使用 DELETE 请求,同样,需要根据 id 进行删除:
语法: DELETE / { 索引库名 } / _doc / id
示例: DELETE / apesource / _doc / 1
结果: result:deleted

2.7.4 修改文档

修改有两种方式:
  • 全量修改:直接覆盖原来的文档
  • 增量修改:修改文档中的部分字段

2.7.4.1 全量修改

全量修改是覆盖原来的文档,其本质是:
  • 根据指定的id删除文档
  • 新增一个相同id的文档
注意 :如果根据 id 删除时, id 不存在,第二步的新增也会执行,也就从修改变成了新增操作了。
语法:
PUT /{索引库名}/_doc/文档id
{
    "字段1": "值1",
    "字段2": "值2",
    // ... 略
}

2.7.4.2 增量修改

增量修改是只修改指定 id 匹配的文档中的部分字段。
语法:
POST /{索引库名}/_update/文档id
{
    "doc": {
        "字段名": "新的值",
    }
}

2.7.5 总结

  • 创建文档:POST /{索引库名}/_doc/文档id { json文档 }
  • 查询文档:GET /{索引库名}/_doc/文档id
  • 删除文档:DELETE /{索引库名}/_doc/文档id
  • 修改文档:
    • 全量修改:PUT /{索引库名}/_doc/文档id { json文档 }
    • 增量修改:POST /{索引库名}/_update/文档id { "doc": {字段}}

 3. ResrAPI

ES 官方提供了各种不同语言的客户端,用来操作 ES 。这些客户端的本质就是组装 DSL 语句,通过 http 请求发送给ES
官方文档地址: https://www.elastic.co/guide/en/elasticsearch/client/index.html
其中的 Java Rest Client 又包括两种:
  • Java Low Level Rest Client
  • Java High Level Rest Client

我们学习的是Java HighLevel Rest Client客户端API

3.1 初始化RestClient

elasticsearch 提供的 API 中,与 elasticsearch 一切交互都封装在一个名为 RestHighLevelClient 的类中,必须先完成这个对象的初始化,建立与elasticsearch 的连接。
分为三步:
1、 引入 es RestHighLevelClient 依赖:
<dependency>
    <groupId>org.elasticsearch.client</groupId>
    <artifactId>elasticsearch-rest-high-level-client</artifactId>
</dependency>

2、因为SpringBoot默认的ES版本是7.6.2,所以我们需要覆盖默认的ES版本:

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

3、初始化RestHighLevelClient

初始化的部分代码如下:

RestHighLevelClient client = new RestHighLevelClient(RestClient.builder(
HttpHost.create("http://localhost:9200")
));
这里为了单元测试方便,我们创建一个测试类 HotelIndexTest ,然后将初始化的代码编写在 @BeforeEach方法中:
private RestHighLevelClient client;

    @BeforeEach
    public void setUp(){
        this.client = new RestHighLevelClient(RestClient.builder(HttpHost.create("http://localhost:9200")));
    }

    @AfterEach
    void tearDown() throws IOException {
        this.client.close();
    }

3.2 创建索引库

创建索引库的 API 如下:
创建一个类,定义 mapping 映射的 JSON 字符串常量:
package com.itzhi.common;

/**
 * @author lizhihui
 * @version 1.0
 * @since 2023/8/11
 */
public class HotelConstants {
    public static final String MAPPING_TEMPLATE = "{\n" +
            "  \"mappings\": {\n" +
            "    \"properties\": {\n" +
            "      \"id\": {\n" +
            "        \"type\": \"keyword\"\n" +
            "      },\n" +
            "      \"name\":{\n" +
            "        \"type\": \"text\",\n" +
            "        \"analyzer\": \"ik_max_word\",\n" +
            "        \"copy_to\": \"all\"\n" +
            "      },\n" +
            "      \"address\":{\n" +
            "        \"type\": \"keyword\",\n" +
            "        \"index\": false\n" +
            "      },\n" +
            "      \"price\":{\n" +
            "        \"type\": \"integer\"\n" +
            "      },\n" +
            "      \"score\":{\n" +
            "        \"type\": \"integer\"\n" +
            "      },\n" +
            "      \"brand\":{\n" +
            "        \"type\": \"keyword\",\n" +
            "        \"copy_to\": \"all\"\n" +
            "      },\n" +
            "      \"city\":{\n" +
            "        \"type\": \"keyword\",\n" +
            "        \"copy_to\": \"all\"\n" +
            "      },\n" +
            "      \"starName\":{\n" +
            "        \"type\": \"keyword\"\n" +
            "      },\n" +
            "      \"business\":{\n" +
            "        \"type\": \"keyword\"\n" +
            "      },\n" +
            "      \"location\":{\n" +
            "        \"type\": \"geo_point\"\n" +
            "      },\n" +
            "      \"pic\":{\n" +
            "        \"type\": \"keyword\",\n" +
            "        \"index\": false\n" +
            "      },\n" +
            "      \"all\":{\n" +
            "        \"type\": \"text\",\n" +
            "        \"analyzer\": \"ik_max_word\"\n" +
            "      }\n" +
            "    }\n" +
            "  }\n" +
            "}";
}
hotel-demo 中的 HotelIndexTest 测试类中,编写单元测试,实现创建索引:
    // 创建索引库
    @Test
    public void createHotelIndex() throws IOException {
        CreateIndexRequest request = new CreateIndexRequest("hotels");
        request.source(HotelConstants.MAPPING_TEMPLATE, XContentType.JSON);
        client.indices().create(request,RequestOptions.DEFAULT);
    }

3.3 删除索引库

hotel-demo 中的 HotelIndexTest 测试类中,编写单元测试,实现删除索引:
    // 删除索引库
    @Test
    public void testDeleteHotelIndex() throws IOException {
        DeleteIndexRequest request = new DeleteIndexRequest("hotels");

        client.indices().delete(request,RequestOptions.DEFAULT);
    }

3.4 判断索引库是否存在

    // 查找索引是否存在
    @Test
    public void testExistsHotelIndex() throws IOException {
        // 1、创建request对象
        GetIndexRequest request = new GetIndexRequest("hotels");
        boolean exists = client.indices().exists(request, RequestOptions.DEFAULT);
        System.err.println(exists ? "索引库已经存在!" : "索引库不存在!");
    }

3.5 总结

JavaRestClient 操作 elasticsearch 的流程基本类似。核心是 client.indices() 方法来获取索引库的操作对象。
索引库操作的基本步骤:
  • 初始化RestHighLevelClient
  • 创建XxxIndexRequestXXXCreateGetDelete
  • 准备DSL Create时需要,其它是无参)
  • 发送请求。调用RestHighLevelClient#indices().xxx()方法,xxxcreateexistsdelete

4. RestClient操作文档 

4.1 新增文档

与数据库相关的实体类
/**
 * @author lizhihui
 * @version 1.0
 * @since 2023/8/11
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
@TableName("tb_hotel")
public class Hotel {

    @TableId(value = "id",type = IdType.INPUT)
    private Long id;
    @TableField("name")
    private String name;
    @TableField("address")
    private String address;
    @TableField("price")
    private Integer price;
    @TableField("score")
    private Integer score;
    @TableField("brand")
    private String brand;
    @TableField("city")
    private String city;
    @TableField("starName")
    private String starName;
    @TableField("business")
    private String business;
    @TableField("longitude")
    private String longitude;//经度
    @TableField("latitude")
    private String latitude;//纬度
    @TableField("pic")
    private String pic;
}
我们需要定义一个新的类型,与索引库结构吻合:
ES 索引库设计实体类
  • longitudelatitude需要合并为location
/**
 * @author lizhihui
 * @version 1.0
 * @since 2023/8/11
 */
@Data
@NoArgsConstructor
@ToString
public class HotelDoc {

    private Long id;
    private String name;
    private String address;
    private Integer price;
    private Integer score;
    private String brand;
    private String city;
    private String starName;
    private String business;
    private String location;
    private String pic;


    public HotelDoc(Hotel hotel) {
        this.id = hotel.getId();
        this.name = hotel.getName();
        this.address = hotel.getAddress();
        this.price = hotel.getPrice();
        this.score = hotel.getScore();
        this.brand = hotel.getBrand();
        this.city = hotel.getCity();
        this.starName = hotel.getStarName();
        this.business = hotel.getBusiness();
        this.location = hotel.getLatitude() + ", " + hotel.getLongitude();
        this.pic = hotel.getPic();
    }
}
hotel-demo HotelDocumentTest 测试类中,编写单元测试:
    // 添加一个文档到es
    @Test
    public void testAddDocument() throws IOException {
        Hotel hotel = service.getById(197837109);
        HotelDoc hotelDoc = new HotelDoc(hotel);
        String json = JSON.toJSONString(hotelDoc);
        IndexRequest request = new IndexRequest("hotels").id(hotelDoc.getId().toString());
        request.source(json, XContentType.JSON);
        client.index(request, RequestOptions.DEFAULT);
    }

4.2 查询文档

hotel-demo HotelDocumentTest 测试类中,编写单元测试:
    // 根据id查找一个文档
    @Test
    public void testGetDocument() throws IOException {
        GetRequest request = new GetRequest("hotels", "197837109");
        GetResponse response = client.get(request, RequestOptions.DEFAULT);
        String json = response.getSourceAsString();
        HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);
        System.out.println(hotelDoc);
    }

4.3 删除文档

hotel-demo HotelDocumentTest 测试类中,编写单元测试:
    // 根据id删除一个文档
    @Test
    public void testDeleteDocument() throws IOException {
        DeleteRequest request = new DeleteRequest("hotels", "197837109");

        client.delete(request, RequestOptions.DEFAULT);
    }

4.4 修改文档

修改我们讲过两种方式:
  • 全量修改:本质是先根据id删除,再新增
  • 增量修改:修改文档中的指定字段值
  • RestClientAPI中,全量修改与新增的API完全一致

hotel-demoHotelDocumentTest测试类中,编写单元测试:

    // 根据id修改文档
    @Test
    public void testUpdateDocument() throws IOException {
        UpdateRequest request = new UpdateRequest("hotels", "197837109");
        request.doc("name", "W酒店",
                "city", "西安",
                "price", "2000",
                "starName", "五星级");
        client.update(request, RequestOptions.DEFAULT);
    }

4.5 批量导入文档

hotel-demo HotelDocumentTest 测试类中,编写单元测试:
    // 批量添加文档
    @Test
    public void testBulkRequest() throws IOException {
        List<Hotel> list = service.list();
        BulkRequest request = new BulkRequest();

        for (Hotel hotel : list) {
            HotelDoc hotelDoc = new HotelDoc(hotel);

            request.add(new IndexRequest("hotels")
                    .id(hotelDoc.getId().toString())
                    .source(JSON.toJSONString(hotelDoc), XContentType.JSON));
        }

        client.bulk(request,RequestOptions.DEFAULT);
    }

4.6 总结

文档操作的基本步骤:
  • 初始化RestHighLevelClient
  • 创建XxxRequestXXXIndexGetUpdateDeleteBulk
  • 准备参数(IndexUpdateBulk时需要)
  • 发送请求。调用RestHighLevelClient#.xxx()方法,xxxindexgetupdatedeletebulk
  • 解析结果(Get时需要)

5. ElasticSearch查询

elasticsearch的查询依然是基于JSON风格的DSL来实现的。

5.1 DSL查询文档

Elasticsearch 提供了基于 JSON DSL Domain Specifific 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

5.2 RestClient查询文档

文档的查询同样适用  RestHighLevelClient 对象,基本步骤包括:
  1. 准备Request对象
  2. 准备请求参数
  3. 发起请求
  4. 解析响应
我们以 match_all 查询为例 完整代码如下:
// 查询所有
    @Test
    public void testMatchAll() throws IOException {
        SearchRequest request = new SearchRequest("hotels");

        request.source().query(QueryBuilders.matchAllQuery());

        SearchResponse response = client.search(request,RequestOptions.DEFAULT);
        show(response);
    }

    //查询all字段内容中有如家的(or拼接多条件)
    @Test
    public void testMatch() throws IOException {
        SearchRequest request = new SearchRequest("hotels");

        request.source().query(QueryBuilders.matchQuery("all","如家"));

        SearchResponse response =  client.search(request,RequestOptions.DEFAULT);
        show(response);
    }

    //查询name,business字段内容中有如家的
    @Test
    void testMultiMatch() throws IOException {
        // 1.准备Request
        SearchRequest request = new SearchRequest("hotels");
        // 2.准备DSL 参数1:字段  参数2:数据
        request.source()
                .query(QueryBuilders.multiMatchQuery("如家", "name","business"));
        // 3.发送请求
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);
        // 4.解析响应
        show(response);
    }

    // 词条查询
    @Test
    public void testTermQuery() throws IOException{
        SearchRequest request = new SearchRequest("hotels");

        request.source().query(QueryBuilders.termQuery("city","上海"));
        SearchResponse response = client.search(request,RequestOptions.DEFAULT);
        show(response);
    }

    //范围查询
    @Test
    void testRangeQuery() throws IOException {
        // 1.准备Request
        SearchRequest request = new SearchRequest("hotels");
        // 2.准备DSL,QueryBuilders构造查询条件
        request.source()
                .query(QueryBuilders.rangeQuery("price").gte(100).lte(150));
        // 3.发送请求
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);
        show(response);
    }

    @Test
    void testBool() throws IOException {
        // 1.准备request
        SearchRequest request = new SearchRequest("hotels");
//      布尔查询是一个或多个查询子句的组合,子查询的组合方式有:
//      must:必须匹配每个子查询,类似“与”
//      should:选择性匹配子查询,类似“或”
//      must_not:必须不匹配,不参与算分,类似“非”
//      filter:必须匹配,类似“与”,不参与算分
//        一般搜索框用must,选择条件使用filter

        // 2.准备请求参数(and拼接)
//        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
//        // 2.1.must
//        boolQuery.must(QueryBuilders.termQuery("city", "上海"));
//        // 2.2.filter小于等于
//        boolQuery.filter(QueryBuilders.rangeQuery("price").lte(260));
//
//        request.source().query(boolQuery);

        //方式2
        request.source().query(
                QueryBuilders.boolQuery()
                        .must(QueryBuilders.termQuery("city", "上海"))
                        .filter(QueryBuilders.rangeQuery("price").lte(260))
        );
        // 3.发送请求,得到响应
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);
        // 4.结果解析
        show(response);
    }

    @Test
    void testPageAndSort() throws IOException {
        // 页码,每页大小
        int page = 1, size = 20;
        // 查询条件
        String searchName = "如家";
//        String searchName = null;

        // 1.准备Request
        SearchRequest request = new SearchRequest("hotels");
        // 2.准备DSL
        // 2.1.query
        if(searchName == null){
            request.source().query(QueryBuilders.matchAllQuery());
        }else{
            request.source().query(QueryBuilders.matchQuery("name", searchName));
        }
        // 2.2.分页 from、size
        request.source().from((page - 1) * size).size(size);
        //2.3.排序
        request.source().sort("price", SortOrder.DESC);

        // 3.发送请求
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);
        // 4.解析响应
        show(response);

    }

    // 解析响应对象
    public void show(SearchResponse response){
        // 解析响应
        SearchHits searchHits = response.getHits();
        // 获取总条数
        long total = searchHits.getTotalHits().value;
        System.out.println("共搜到" + total + "条数据");
        // 文档数组
        SearchHit[] hits = searchHits.getHits();
        // 遍历
        for (SearchHit s : hits){
            String json = s.getSourceAsString();
            HotelDoc hotelDoc = JSON.parseObject(json,HotelDoc.class);
            System.out.println("HotelDoc = " + hotelDoc);
        }
    }

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

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

相关文章

软考(一)进制的表示(二进制,八进制,十进制,十六进制)

进制的转换 一、进制的表示 二进制&#xff08;B&#xff09;&#xff1a; 0 , 1 , 10 , 11 , 100 , 101 , 110 , 111 , 1000 0,1,10,11,100,101,110,111,1000 0,1,10,11,100,101,110,111,1000 对应的十进制是&#xff1a; 0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 0,1,2,3,4,5,6…

视频汇聚/视频监控管理平台EasyCVR接入海康SDK协议后无法播放该如何解决?

开源EasyDarwin视频监控/安防监控/视频汇聚EasyCVR能在复杂的网络环境中&#xff0c;将分散的各类视频资源进行统一汇聚、整合、集中管理&#xff0c;在视频监控播放上&#xff0c;视频安防监控汇聚平台可支持1、4、9、16个画面窗口播放&#xff0c;可同时播放多路视频流&#…

长安链并行调度机制(2):DAG构建和从节点执行流程

长安链采用高效的并行调度方式执行交易&#xff0c;了解长安链交易调度、冲突检测和DAG构建流程有助于开发者更好地理解长安链并行调度的运行机制&#xff0c;帮助开发者编写高质量、低冲突的智能合约&#xff0c;更好地构建区块链应用。 上一篇内容我们说明了长安链交易调度、…

STM32驱动SD卡(SPI)方式

外观 代码(免费分享) 接线 5V供电 CS接PA3 剩下如图按照硬件SPI1接线 注意事项 使用杜邦线接线非常不稳定&#xff01;&#xff01;&#xff01; 使用杜邦线接线非常不稳定&#xff01;&#xff01;&#xff01; 使用杜邦线接线非常不稳定&#xff01;&#xff01;&#…

如何将 Animate 动画与 After Effects 中的 Cinema 4D 渲染合并?

如何将手动动画 2D 元素添加到 3D 渲染中&#xff0c;有多种方法可以做到这一点&#xff0c;但您需要确保在动画的两侧都进行设置&#xff0c;以确保在合成时能够充分利用资源。上面的视频确实贯穿了一个非常实用且高效的工作流程&#xff0c;以实现正确的这些效果。 创建参考…

沉浸式VR虚拟实景样板间降低了看房购房的难度

720 全景是一种以全景视角为特点的虚拟现实展示方式&#xff0c;它通过全景图像和虚拟现实技术&#xff0c;将用户带入一个仿佛置身其中的沉浸式体验中。720 全景可以应用于旅游、房地产、展览等多个领域&#xff0c;为用户提供更为直观、真实的体验。 在房地产领域&#xff0c…

乡村振兴战略下传统村落文化旅游设计书辉瑞

乡村振兴战略下传统村落文化旅游设计书辉瑞

vue3+ts+tinynce富文本编辑器+htmlDocx+file-saver 配合实现word下载

vue3 请下载html-docx-js-typescript&#xff0c;否则会报错类型问题 //报告导出word import * as htmlDocx from "html-docx-js-typescript";//ts-ignore import { saveAs } from file-saver// 下载文件&#xff0c; const downloadFile (row)> {try {const co…

24个非常实用的Python小技巧

嗨喽&#xff0c;大家好呀~这里是爱看美女的茜茜呐 1.唯一性 以下方法可以检查给定列表是否有重复的地方&#xff0c;可用set&#xff08;&#xff09;的属性将其从列表中删除。 x [1,1,2,2,3,2,3,4,5,6] y [1,2,3,4,5] len(x) len(set(x)) # False len(y) len(set(y)) # Tr…

压力变送器与传统压力表相比,有哪些优势?

在压力变送器还未普及的时候&#xff0c;工业自动化生产中的压力数据普遍采用压力表进行数据采集&#xff0c;但是压力表数据在使用的过程中&#xff0c;经常存在记录不方便、校验周期短、故障率高的问题&#xff0c;随着数字化在工业生产中的广泛应用&#xff0c;压力变送器逐…

[EasyX库安装介绍讲解】超详细入门级

基本说明 EasyX 是针对 C 的图形库&#xff0c;可以帮助 C/C 初学者快速上手图形和游戏编程。 比如&#xff0c;可以基于 EasyX 图形库很快的用几何图形画一个房子&#xff0c;或者一辆移动的小车&#xff0c;可以编写俄罗斯方块、贪吃蛇、黑白棋等小游戏&#xff0c;可以练习…

安防视频监控/视频集中存储/云存储平台EasyCVR无法播放HLS协议该如何解决?

视频云存储/安防监控EasyCVR视频汇聚平台基于云边端智能协同&#xff0c;支持海量视频的轻量化接入与汇聚、转码与处理、全网智能分发、视频集中存储等。音视频流媒体视频平台EasyCVR拓展性强&#xff0c;视频能力丰富&#xff0c;具体可实现视频监控直播、视频轮播、视频录像、…

Andorid 属性动画ObjectAnimation整理

属性动画相关内容可参考官网 动画资源 属性动画概览 来自官网的说明&#xff0c; 属性动画与视图动画的区别 视图动画系统仅提供为 View 对象添加动画效果的功能&#xff0c;因此&#xff0c;如果您想为非 对象添加动画效果&#xff0c;则必须实现自己的代码才能做到。视图动…

微信开发之一键创建标签的技术实现

简要描述&#xff1a; 添加标签 请求URL&#xff1a; http://域名地址/addContactLabel 请求方式&#xff1a; POST 请求头Headers&#xff1a; Content-Type&#xff1a;application/jsonAuthorization&#xff1a;login接口返回 参数&#xff1a; 参数名必选类型说明…

高校实验室预约平台

高校实验室预约平台 我们​正在定制开发的软件&#xff0c;资源都在完成数字化管理&#xff0c;然后向社会开放… 每个行业走在向数字化深化&#xff0c;昨天看到一个数字&#xff0c;美国企业SAAS软件的渗透率为75%&#xff0c;中国企业还只有15%&#xff0c;中国企业数字化还…

同创永益入选首批“金融数字韧性与混沌工程实践试点机构”

8月16日下午&#xff0c;由北京国家金融科技认证中心、北京国家金融标准化研究院联合主办的“传递信任 服务发展”金融科技标准认证生态大会在太原成功举办。中国金融电子化集团有限公司党委书记、董事长周逢民&#xff0c;中国科学院院士冯登国&#xff0c;中国工商银行首席技…

如何制作党建专题汇报片

通过展示党组织的凝聚力和战斗力&#xff0c;增强党员的组织归属感和团结合作意识。通过宣传片&#xff0c;可以加强党组织的凝聚力&#xff0c;推动党的事业发展。制作党建专题汇报片需要一定的前期准备和后期制作技巧。下面是由深圳党建专题汇报片制作公司老友记小编为您整理…

kubesphere安装Maven+JDK17 流水线打包

kubesphere 3.4.0版本&#xff0c;默认支持的jav版本是8和11&#xff0c;不支持17 。需要我们自己定义JenKins Agent 。方法如下&#xff1a; 一、构建镜像 1、我们需要从Jenkins Agent的github仓库拉取master最新源码&#xff0c;最新源码里已经支持jdk17了。 git clone ht…

Spring 如何解决循环依赖问题 - 三级缓存

1. 什么是循环依赖问题 ? 循环依赖问题是指对象与对象之间存在相互依赖关系&#xff0c;而且形成了一个闭环&#xff0c;导致两个或多个对象都无法准确的完成对象的创建和初始化。 两个对象间的循环依赖&#xff1a; 多个对象间的循环依赖 &#xff1a; 解决 Spring 中的循环…

MobaXtermV10.7安装步骤

目录 1&#xff0c;打开​编辑 2&#xff0c;填写自己的虚拟机IP和用户名&#xff0c;点机OK 3,设置 MobaXterm是一款增强型远程连接工具&#xff0c;主要用于Windows的增强终端&#xff0c;带有X11服务器、选项卡式SSH客户端、网络工具等。在一个Windows应用程序中&#xff…