Elasticsearch 学习(二).实战使用

Elasticsearch 学习(二).实战使用

参考:http://www.passjava.cn/#/01.PassJava/02.PassJava_Architecture/15.Elasticsearch%E5%AE%9E%E6%88%98

一、Elasticsearch 组件库介绍

全文检索是什么:

全文检索: 指以全部文本信息作为检索对象的一种信息检索技术。而我们使用的数据库,如 Mysql,MongoDB 对文本信息检索能力特别是中文检索并没有 ES 强大。所以我们来看下 ES 在项目中是如何来代替 SQL 来工作的。

使用的 Elasticsearch 服务是 7.4.2 的版本,然后采用官方提供的 Elastiscsearch-Rest-Client 库来操作 ES,而且官方库的 API 上手简单。

该组件库的官方文档地址:

https://www.elastic.co/guide/en/elasticsearch/client/java-rest/current/java-rest-high.html

另外这个组件库是支持多种语言的:

在这里插入图片描述

注意:Elasticsearch Clients 就是指如何用 API 操作 ES 服务的组件库。

可能有同学会提问,Elasticsearch 的组件库中写着 JavaScript API,是不是可以直接在前端访问 ES 服务?可以是可以,但是会暴露 ES 服务的端口和 IP 地址,会非常不安全。所以我们还是用后端服务来访问 ES 服务。

我们这个项目是 Java 项目,自然就是用上面的两种:Java Rest Client 或者 Java API。我们先看下 Java API,但是会发现已经废弃了。如下图所示:

在这里插入图片描述

只能用 Java REST Client 了。而它又分成两种:高级和低级的。

高级包含更多的功能,如果把高级比作MyBatis的话,那么低级就相当于JDBC。所以我们用高级的 Client。

在这里插入图片描述

二、整合检索服务

把检索服务单独作为一个服务。就称作 passjava-search 模块吧。

1.1 添加搜索服务模块
  • 创建 passjava-search 模块。

首先我们在 PassJava-Platform 模块创建一个 搜索服务模块 passjava-search。然后勾选 spring web 服务。如下图所示。

第一步:新建一个模块 搜索服务模块

group: com.ung.passjava
artifact:passjava-search
name:passjava-search
description:搜索服务
package:com.ung.passjava.search

在这里插入图片描述

1.2 配置 Maven 依赖

选择 Web->Spring Web 依赖,然后点击 Next

进入到 ES 官方网站,可以看到有低级和高级的 Rest Client,我们选择高阶的(High Level Rest Client)。然后进入到高阶 Rest Client 的 Maven 仓库。官网地址如下所示:

https://www.elastic.co/guide/en/elasticsearch/client/java-rest/7.9/index.html

在这里插入图片描述

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.4.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <groupId>com.ung.passjava</groupId>
    <artifactId>passjava-search</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>passjava-search</name>
    <description>搜索服务</description>



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

    <dependencies>
        <dependency>
            <groupId>com.ung.passjava</groupId>
            <artifactId>passjava-common</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>

        <!--入 ES 的高阶客户端-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

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

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

最后刷新maven依赖可以看到

在这里插入图片描述

1.3 注册搜索服务到注册中心

修改配置文件:src/main/resources/application.properties。配置应用程序名、注册中心地址、注册中心的命名中间。

#服务名
spring.application.name=passjava-search
#注册中心地址
spring.cloud.nacos.config.server-addr=127.0.0.1:8848
#配置中心的命名空间
spring.cloud.nacos.config.namespace=passjava-search
#服务端口号
server.port=17000

启动类添加服务发现注解:@EnableDiscoveryClient。这样 passjava-search 服务就可以被注册中心发现了。

因 Common 模块依赖数据源,但 search 模块不依赖数据源,所以 search 模块需要移除数据源依赖:

exclude = DataSourceAutoConfiguration.class

以上的两个注解如下所示:

@EnableDiscoveryClient
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
public class PassjavaSearchApplication {
    public static void main(String[] args) {
        SpringApplication.run(PassjavaSearchApplication.class, args);
    }
}

接下来我们添加一个 ES 服务的专属配置类,主要目的是自动加载一个 ES Client 来供后续 ES API 使用,不用每次都 new 一个 ES Client。

1.4 添加 ES 配置类

配置类:PassJavaElasticsearchConfig.java

核心方法就是 RestClient.builder 方法,设置好 ES 服务的 IP 地址、端口号、传输协议就可以了。最后自动加载了 RestHighLevelClient。

package com.ung.passjava.search.config;

import org.apache.http.HttpHost;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class PassJavaElasticsearchConfig {

    @Bean
    // 给容器注册一个 RestHighLevelClient,用来操作 ES
    // 参考官方文档:https://www.elastic.co/guide/en/elasticsearch/client/java-rest/7.9/java-rest-high-getting-started-initialization.html
    public RestHighLevelClient restHighLevelClient() {
        return new RestHighLevelClient(
                RestClient.builder(
                        new HttpHost("192.168.2.135", 9200, "http")));
    }
}

接下来我们测试下 ES Client 是否自动加载成功

1.5 测试 ES Client 自动加载

在测试类 PassjavaSearchApplicationTests 中编写测试方法,打印出自动加载的 ES Client。期望结果是一个 RestHighLevelClient 对象。

@SpringBootTest
class PassjavaSearchApplicationTests {


    @Qualifier("restHighLevelClient")
    @Autowired
    private RestHighLevelClient client;

    @Test
    void contextLoads() {
        System.out.println(client);
    }

}

结果打印成功

在这里插入图片描述

1.6 测试 ES 简单插入数据

测试方法 testIndexData, users 索引在我的 ES 中是没有记录的,所以期望结果是 ES 中新增了一条 users 数据。

   @Data
    class User {
        private String userName;
        private Integer age;
        private String gender;

    }


    /**
     * 测试存储数据到 ES。
     */
    @Test
    public void testIndexData() throws IOException {
        IndexRequest request = new IndexRequest("users");
        request.id("1"); // 文档的 id

        //构造 User 对象
        User user = new User();
        user.setUserName("PassJava");
        user.setAge(18);
        user.setGender("Man");

        //User 对象转为 JSON 数据
        String jsonString = JSON.toJSONString(user);

        // JSON 数据放入 request 中
        request.source(jsonString, XContentType.JSON);

        // 执行插入操作
        IndexResponse response = client.index(request, RequestOptions.DEFAULT);

        System.out.println(response);
    }

执行 test 方法,我们可以看到控制台输出以下结果,说明数据插入到 ES 成功。结果中的 result 字段为 created

在这里插入图片描述

我们再来到 ES 中看下 users 索引中数据。查询 users 索引:

GET users/_search

可以看到结果成功查出

在这里插入图片描述

1.7 测试 ES 查询复杂语句

示例:搜索 bank 索引,address 字段中包含 road 的所有人的年龄分布 ( 前 10 条 ) 以及平均年龄,以及平均薪资。

1.7.1 构造检索条件

我们可以参照官方文档给出的示例来创建一个 SearchRequest 对象,指定要查询的索引为 bank,然后创建一个 SearchSourceBuilder 来组装查询条件。总共有三种条件需要组装:

  • address 中包含 road 的所有人。
  • 按照年龄分布进行聚合。
  • 计算平均薪资。

代码如下所示

@ToString
@Data
static class BankMember {
    private int account_number;
    private int balance;
    private String firstname;
    private String lastname;
    private int age;
    private String gender;
    private String address;
    private String employer;
    private String email;
    private String city;
    private String state;
}


/**
 * 搜索 address 中包含 road 的所有人的年龄分布 ( 前 10 条 ) 以及平均年龄,平均薪资
 *
 * @throws IOException
 */
@Test
public void testSearchData() throws IOException {
    //新建一个查询请求对象
    SearchRequest request = new SearchRequest();
    //设置查询索引是bank
    request.indices("bank");
    SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();


    // 1.1)address 中包含 road 的所有人
    sourceBuilder.query(QueryBuilders.matchQuery("address", "road"));

    // 1.2)按照年龄分布进行聚合
    TermsAggregationBuilder ageAgg = AggregationBuilders.terms("ageAgg").field("age").size(10);
    sourceBuilder.aggregation(ageAgg);

    // 1.3)计算平均薪资
    AvgAggregationBuilder balanceAvg = AggregationBuilders.avg("balanceAvg").field("balance");
    sourceBuilder.aggregation((balanceAvg));

    System.out.println("检索参数:" + sourceBuilder.toString());

    request.source(sourceBuilder);

    // 2、执行检索
    SearchResponse response = client.search(request, RequestOptions.DEFAULT);

    // 3、分析结果
    System.out.println(response.toString());
    // 3.1)获取查到的数据。
    SearchHits hits = response.getHits();
    // 3.2)获取真正命中的结果
    SearchHit[] searchHits = hits.getHits();
    // 3.3)遍历命中结果
    for (SearchHit hit : searchHits) {
        String hitStr = hit.getSourceAsString();
        BankMember bankMember = JSON.parseObject(hitStr, BankMember.class);
        System.out.println(bankMember);
    }
    // 3.4)获取聚合信息
    // 参考文档:https://www.elastic.co/guide/en/elasticsearch/client/java-rest/current/java-rest-high-search.html
    Aggregations aggregations = response.getAggregations();
    Terms ageAgg1 = aggregations.get("ageAgg");
    for (Terms.Bucket bucket : ageAgg1.getBuckets()) {
        String keyAsString = bucket.getKeyAsString();
        System.out.println("用户年龄: " + keyAsString + " 人数:" + bucket.getDocCount());
    }

    Avg balanceAvg1 = aggregations.get("balanceAvg");
    System.out.println("平均薪资:" + balanceAvg1.getValue());
}

打印结果:

检索简化参数:
GET bank/_search
{
    "query": {
        "match": {
            "address": "road"
        }
    },
    "aggs": {
        "ageAggr": {
            "terms": {
                "field": "age",
                "size": 10
            }
        },
        "ageAvg": {
            "avg": {
                "field": "age"
            }
        },
        "balanceAvg": {
            "avg": {
                "field": "balance"
            }
        }
    }
}

在这里插入图片描述

17.2 获取命中记录的详情
// 3.1)获取查到的数据。
SearchHits hits = response.getHits();
// 3.2)获取真正命中的结果
SearchHit[] searchHits = hits.getHits();

我们可以通过遍历 searchHits 的方式打印出所有命中结果的详情。

// 3.3)、遍历命中结果
for (SearchHit hit: searchHits) {
    String hitStr = hit.getSourceAsString();
    BankMember bankMember = JSON.parseObject(hitStr, BankMember.class);
}

拿到每条记录的 hitStr 是个 JSON 数据,如下所示:

{
    "account_number": 431,
    "balance": 13136,
    "firstname": "Laurie",
    "lastname": "Shaw",
    "age": 26,
    "gender": "F",
    "address": "263 Aviation Road",
    "employer": "Zillanet",
    "email": "laurieshaw@zillanet.com",
    "city": "Harmon",
    "state": "WV"
}

而 BankMember 是根据返回的结果详情定义的的 JavaBean。可以通过工具自动生成。在线生成 JavaBean 的网站如下:

https://www.bejson.com/json2javapojo/new/

把这个 JavaBean 加到 PassjavaSearchApplicationTests 类中:

@ToString
@Data
static class BankMember {
    private int account_number;
    private int balance;
    private String firstname;
    private String lastname;
    private int age;
    private String gender;
    private String address;
    private String employer;
    private String email;
    private String city;
    private String state;
}

然后将 bankMember 打印出来:

System.out.println(bankMember);

在这里插入图片描述

得到的结果确实是我们封装的 BankMember 对象,而且里面的属性值也都拿到了。

1.7.3 获取年龄分布聚合信息

ES 返回的 response 中,年龄分布的数据是按照 ES 的格式返回的,如果想按照我们自己的格式来返回,就需要将 response 进行处理。

如下图所示,这个是查询到的年龄分布结果,我们需要将其中某些字段取出来,比如 buckets,它代表了分布在 21 岁的有 4 个。

在这里插入图片描述

实现:

Aggregations aggregations = response.getAggregations();
Terms ageAgg1 = aggregations.get("ageAgg");
for (Terms.Bucket bucket : ageAgg1.getBuckets()) {
    String keyAsString = bucket.getKeyAsString();
    System.out.println("用户年龄: " + keyAsString + " 人数:" + bucket.getDocCount());
}

最后结果打印输出:

在这里插入图片描述

1.7.4 获取平均薪资聚合信息

现在来看看平均薪资如何按照所需的格式返回,ES 返回的结果如下图所示,我们需要获取 balanceAvg 字段的 value 值

在这里插入图片描述

代码实现:

Avg balanceAvg1 = aggregations.get("balanceAvg");
System.out.println("平均薪资:" + balanceAvg1.getValue());

打印结果如下,平均薪资 28578 元。

在这里插入图片描述

三、实战:同步 ES 数据

3.1 定义检索模型

PassJava 这个项目可以用来配置题库,如果我们想通过关键字来搜索题库,该怎么做呢?

类似于百度搜索,输入几个关键字就可以搜到关联的结果,我们这个功能也是类似,通过 Elasticsearch 做检索引擎,后台管理界面和小程序作为搜索入口,只需要在小程序上输入关键字,就可以检索相关的题目和答案。

首先我们需要把题目和答案保存到 ES 中,在存之前,第一步是定义索引的模型,如下所示,模型中有 titleanswer 字段,表示题目和答案。

"id": {
    "type": "long"
},
"title": {
    "type": "text",
    "analyzer": "ik_smart"
},
"answer": {
    "type": "text",
    "analyzer": "ik_smart"
},
"typeName": {
    "type": "keyword"
}
3.2 在 ES 中创建索引

上面我们已经定义了索引结构,接着就是在 ES 中创建索引。

在 Kibana 控制台中执行以下语句:

PUT question
{
    "mappings" : {
        "properties": {
              "id": {
                  "type": "long"
              },
              "title": {
                  "type": "text",
                  "analyzer": "ik_smart"
              },
              "answer": {
                  "type": "text",
                  "analyzer": "ik_smart"
              },
              "typeName": {
                  "type": "keyword"
              }
        }
  }
}

在这里插入图片描述

通过以下命令来查看 question 索引是否在 ES 中:

GET _cat/indices

结果:

在这里插入图片描述

3.3 定义 ES model

上面我们定义 ES 的索引,接着就是定义索引对应的模型,将数据存到这个模型中,然后再存到 ES 中。

ES 模型如下,共四个字段:id、title、answer、typeName。和 ES 索引是相互对应的。

在common模块创建 QuestionEsModel类

@Data
public class QuestionEsModel {
    private Long id;
    private String title;
    private String answer;
    private String typeName;
}

在这里插入图片描述

3.4 触发保存的时机

当我们在后台创建题目或保存题目时,先将数据保存到 mysql 数据库,然后再保存到 ES 中。

如下图所示,在管理后台创建题目时,触发保存数据到 ES 。

在这里插入图片描述

第一步,保存数据到 mysql 中,项目中已经包含此功能,就不再讲解了

直接进入第二步:保存数据到 ES 中。而保存数据到 ES 中,需要将数据组装成 ES 索引对应的数据,所以我用了一个 ES model,先将数据保存到 ES model 中。

3.5 用 model 来组装数据

这里的关键代码时 copyProperties,可以将 question 对象的数据取出,然后赋值到 ES model 中。不过 ES model 中还有些字段是 question 中没有的,所以需要单独拎出来赋值,比如 typeName 字段,question 对象中没有这个字段,它对应的字段是 question.type,所以我们把 type 取出来赋值到 ES model 的 typeName 字段上。

//创建ES model
QuestionEsModel esModel = new QuestionEsModel();

//复制属性
BeanUtils.copyProperties(question, esModel);

//获取题目类型的名称
TypeEntity typeEntity = typeService.getById(question.getType());
String typeName = typeEntity.getType();
//给model 的 typeName 赋值
esModel.setTypeName(typeName);
System.out.println("-----------------esModel:" + esModel);
3.6 保存数据到 ES

我在 passjava-search 微服务中写了一个保存题目的 api 用来保存数据到 ES 中。

使用fegin调用

在这里插入图片描述

创建search用于保存Question的service和实现类

public interface IQuestionService {
    boolean save(QuestionEsModel questionEsModel) throws IOException;
}

实现类

@Service("questionService")
public class QuestionServiceImpl implements IQuestionService {

    @Autowired
    RestHighLevelClient restHighLevelClient;

    @Override
    public boolean save(QuestionEsModel questionEsModel) throws IOException {
        // 创建数据到 ES 中
        IndexRequest indexRequest = new IndexRequest(EsConstant.QUESTION_INDEX);
        //设置id
        indexRequest.id(questionEsModel.getId().toString());
        String data = JSON.toJSONString(questionEsModel);
        indexRequest.source(data, XContentType.JSON);
        IndexResponse response = restHighLevelClient.index(indexRequest, RequestOptions.DEFAULT);
        System.out.println("save*****************questionEsModel:" + response);
        return true;
    }
}

EsConstant

public class EsConstant {
    public static final String QUESTION_INDEX = "question"; // 题目数据在 ES 中的索引
    public static final Integer PAGE_SIZE = 5; // 分页大小
}

passjava-question 使用feign调用

在 QuestionController中修改save方法

    /**
     * 保存
     */
    @RequestMapping("/save")
    //@RequiresPermissions("question:question:save")
    public R save(@Valid @RequestBody QuestionEntity question) {
//        questionService.save(question);
          questionService.saveQuestion(question);
        return R.ok();
    }

QuestionService

public interface QuestionService extends IService<QuestionEntity> {
       boolean saveQuestion(QuestionEntity question);
}

QuestionServiceImpl

@Service("questionService")
public class QuestionServiceImpl extends ServiceImpl<QuestionDao, QuestionEntity> implements QuestionService {

    @Autowired
    TypeService typeService;

    @Autowired
    SearchFeignService searchFeignService;

    @Override
    public boolean saveQuestion(QuestionEntity question) {
         save(question);
        saveEs(question);
        return true;
    }

    /**
     * 同步到es
     *
     * @param question
     * @return
     */
    private boolean saveEs(QuestionEntity question) {
        //创建ES model
        QuestionEsModel esModel = new QuestionEsModel();

        //复制属性
        BeanUtils.copyProperties(question, esModel);

        //获取题目类型的名称
        TypeEntity typeEntity = typeService.getById(question.getType());
        String typeName = typeEntity.getType();
        //给model 的 typeName 赋值
        esModel.setTypeName(typeName);
        System.out.println("-----------------esModel:" + esModel);
        // 调用 passjava-search 服务,将数据发送到 ES 中保存。
        R r = searchFeignService.saveQuestion(esModel);

        System.out.println("r:" + r);

        return true;
    }

}

创建fegin的es的service

@FeignClient("passjava-search")
public interface SearchFeignService {

    @PostMapping("search/question/save")
    R saveQuestion(@RequestBody QuestionEsModel questionEsModel);
}

启动类开启feign

@EnableDiscoveryClient
@EnableFeignClients(basePackages = "com.ung.passjava.question.feign")
@MapperScan("com.ung.passjava.question.dao")
@SpringBootApplication
public class PassjavaQuestionApplication {
    public static void main(String[] args) {
        SpringApplication.run(PassjavaQuestionApplication.class, args);
    }
}

最后启动服务

发起请求

post  http://127.0.0.1:11000/question/v1/admin/question/save


{
        "title": "ccc",
        "answer": "aaa",
        "level": 2,
        "displayOrder": 1,
        "subTitle": "ccc2",
        "type": 1,
        "enable": 1
    }
3.7 检验 ES 中是否创建成功

通过 kibana 的控制台来查看 question 索引中的文档。通过以下命令来查看:

GET question/_search

结果:

{
  "took" : 1030,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 1,
      "relation" : "eq"
    },
    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : "question",
        "_type" : "_doc",
        "_id" : "5",
        "_score" : 1.0,
        "_source" : {
          "answer" : "aaa",
          "id" : 5,
          "title" : "ccc",
          "typeName" : "type名字"
        }
      }
    ]
  }
}
问题

可以重复更新题目吗?

答案是可以的,保存到 ES 的数据是幂等的,因为保存的时候带了一个类似数据库主键的 id。

四、实战:查询 ES 数据

我们已经将数据同步到了 ES 中,现在就是前端怎么去查询 ES 数据中,这里我们还是使用 Postman 来模拟前端查询请求。

4.1 定义请求参数

请求参数我定义了三个:

  • keyword:用来匹配问题或者答案。
  • id:用来匹配题目 id。
  • pageNum:用来分页查询数据。

这里我将这三个参数定义为一个类:

@Data
public class SearchParam {
    private String keyword; // 全文匹配的关键字
    private String id; // 题目 id
    private Integer pageNum; // 查询第几页数据
}
4.2 定义返回参数

返回的 response 我也定义了四个字段:

  • questionList:查询到的题目列表。
  • pageNum:第几页数据。
  • total:查询到的总条数。
  • totalPages:总页数。

定义的类如下所示:

@Data
public class SearchQuestionResponse {
    private List<QuestionEsModel> questionList; // 题目列表
    private Integer pageNum; // 查询第几页数据
    private Long total; // 总条数
    private Integer totalPages; // 总页数
}
4.3 组装 ES 查询参数

调用 ES 的查询 API 时,需要构建查询参数。

组装查询参数的核心代码:

接口 IQuestionSearchService

public interface IQuestionSearchService {

    SearchQuestionResponse search(SearchParam param);
}

QuestionSearchServiceImpl

@Service
public class QuestionSearchServiceImpl implements IQuestionSearchService {


    @Qualifier("restHighLevelClient")
    @Autowired
    private RestHighLevelClient client;

    @Override
    public SearchQuestionResponse search(SearchParam param) {

        SearchQuestionResponse questionResponse = new SearchQuestionResponse();
        SearchResponse searchResponse = null;
        /*
         * 1.动态构建出查询需要的 DSL 语句
         */
        // 1.1) 创建检索请求
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder(); // 构建 DSL 语句
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
        if (!StringUtils.isEmpty(param.getKeyword())) {
            // 1.2) title,answer,typeName 字段用来关键词检索
            boolQuery.must(QueryBuilders.multiMatchQuery(param.getKeyword(),"title", "answer", "typeName"));
        }
        if (param.getId() != null) {
            // 1.3)题目 id 用来精确匹配
            boolQuery.filter(QueryBuilders.termQuery("id", param.getId()));
        }
        sourceBuilder.query(boolQuery);

        // 1.4)分页
        sourceBuilder.from((param.getPageNum() - 1) * EsConstant.PAGE_SIZE);
        sourceBuilder.size(EsConstant.PAGE_SIZE);

        SearchRequest request = new SearchRequest(new String[] {EsConstant.QUESTION_INDEX}, sourceBuilder);

        try {
            // 2、执行检索
            searchResponse = client.search(request, RequestOptions.DEFAULT);

            // 3、分析结果
            System.out.println(searchResponse.toString());
            // 3.1)获取查到的数据。
            SearchHits hits = searchResponse.getHits();
            // 3.2)获取真正命中的结果
            SearchHit[] searchHits = hits.getHits();
            // 3.3)遍历命中结果
            List<QuestionEsModel> questionEsModelList = new ArrayList<>();
            if (hits.getHits() != null && hits.getHits().length > 0) {
                for (SearchHit hit : searchHits) {
                    String hitStr = hit.getSourceAsString();
                    QuestionEsModel questionEsModel = JSON.parseObject(hitStr, QuestionEsModel.class);
                    System.out.println(questionEsModel);
                    questionEsModelList.add(questionEsModel);
                }
                questionResponse.setQuestionList(questionEsModelList);

                // 分页
                long total = hits.getTotalHits().value;
                questionResponse.setTotal(total);
                questionResponse.setPageNum(param.getPageNum());
                int totalPages = (int) total % EsConstant.PAGE_SIZE == 0 ? (int) total / EsConstant.PAGE_SIZE : (int) (total / EsConstant.PAGE_SIZE + 1);
                questionResponse.setTotalPages(totalPages);
            }

        } catch (IOException e) {
            e.printStackTrace();
        }
        return questionResponse;
    }
}

请求参数封装:

  • 第一步:创建检索请求。
  • 第二步:设置哪些字段需要模糊匹配。这里有三个字段:title,answer,typeName。
  • 第三步:设置如何分页。这里分页大小是 5 个。
  • 第四步:调用查询 api。

在这里插入图片描述

4.4 返回结果封装
  • 第一步:获取查到的数据。
  • 第二步:获取真正命中的结果。
  • 第三步:格式化返回的数据。
  • 第四步:组装分页参数。

在这里插入图片描述

4.5 测试 ES 查询
4.5.1 实验一:测试 title 匹配

验证 title 字段是否能匹配到,传的请求参数 keyword = 111,匹配到了 title = 111 的数据,且只有一条。页码 pageNum 我传的 1,表示返回第一页数据。如下图所示:
在这里插入图片描述

4.5.2 实验二:测试 answer 匹配

验证 answer 字段是否能匹配到,传的请求参数 keyword = 测试答案,匹配到了 title = 测试答案的数据,且只有一条,说明查询成功。如下图所示:

在这里插入图片描述

4.5.2 实验三:测试 id 匹配

我们现在想要匹配题目 id 的话,需要传请求参数 id,而且 id 是精确匹配。另外 id 和 keyword 是取并集,所以不能传 keyword 字段。

请求参数 id = 5,返回结果也是 id =5 的数据,说明查询成功。如下图所示:

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值