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 中,在存之前,第一步是定义索引的模型,如下所示,模型中有 title
和 answer
字段,表示题目和答案。
"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 的数据,说明查询成功。如下图所示: