目录
SpringBoot 整合 EleasticSearch 8.0.X 弃用high-level-client
RestAPI 简介
ES官方提供了各种不同语言的客户端,用来操作ES。这些客户端的本质就是组装DSL语句,通过http请求发送给ES。官方文档地址:Elasticsearch Clients | Elastic
1.前提工作 -- 创建索引库
我们根据一个mysql的数据库表来创建对应的索引库
CREATE TABLE `tb_hotel` (
`id` bigint(20) NOT NULL COMMENT '酒店id',
`name` varchar(255) NOT NULL COMMENT '酒店名称;例:7天酒店',
`address` varchar(255) NOT NULL COMMENT '酒店地址;例:航头路',
`price` int(10) NOT NULL COMMENT '酒店价格;例:329',
`score` int(2) NOT NULL COMMENT '酒店评分;例:45,就是4.5分',
`brand` varchar(32) NOT NULL COMMENT '酒店品牌;例:如家',
`city` varchar(32) NOT NULL COMMENT '所在城市;例:上海',
`star_name` varchar(16) DEFAULT NULL COMMENT '酒店星级,从低到高分别是:1星到5星,1钻到5钻',
`business` varchar(255) DEFAULT NULL COMMENT '商圈;例:虹桥',
`latitude` varchar(32) NOT NULL COMMENT '纬度;例:31.2497',
`longitude` varchar(32) NOT NULL COMMENT '经度;例:120.3925',
`pic` varchar(255) DEFAULT NULL COMMENT '酒店图片;例:/img/1.jpg',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
创建的索引库如下:
PUT /hotel
{
"mappings": {
"properties": {
"id": {
"type": "keyword"
},
"name":{
"type": "text",
"analyzer": "ik_max_word",
"copy_to": "all"
},
"address":{
"type": "keyword",
"index": false
},
"price":{
"type": "integer"
},
"score":{
"type": "integer"
},
"brand":{
"type": "keyword",
"copy_to": "all"
},
"city":{
"type": "keyword",
"copy_to": "all"
},
"starName":{
"type": "keyword"
},
"business":{
"type": "keyword"
},
"location":{
"type": "geo_point"
},
"pic":{
"type": "keyword",
"index": false
},
"all":{
"type": "text",
"analyzer": "ik_max_word"
}
}
}
}
几个特殊字段说明:
-
location:地理坐标,里面包含精度、纬度
-
all:一个组合字段,其目的是将多字段的值 利用copy_to合并,提供给用户搜索
地理坐标说明:
copy_to说明:
2.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.1</elasticsearch.version>
</properties>
3)初始化RestHighLevelClient:
初始化的代码如下:
RestHighLevelClient client = new RestHighLevelClient(RestClient.builder(
HttpHost.create("http://192.168.150.101:9200")
));
创建索引库
代码分为三步:
-
1)创建Request对象。因为是创建索引库的操作,因此Request是CreateIndexRequest。
-
2)添加请求参数,其实就是DSL的JSON参数部分。因为json字符串很长,这里是定义了静态字符串常量MAPPING_TEMPLATE,让代码看起来更加优雅。
-
3)发送请求,client.indices()方法的返回值是IndicesClient类型,封装了所有与索引库操作有关的方法。
code:
@Test
void createHotelIndex() throws IOException {
CreateIndexRequest request = new CreateIndexRequest("hotel");
request.source(MAPPING_TEMPLATE, XContentType.JSON);
client.indices().create(request, RequestOptions.DEFAULT);
}
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" +
"}";
}
删除索引库
删除索引库的DSL语句非常简单
DELETE /hotel
@Test
void testDeleteHotelIndex() throws IOException {
// 1.创建Request对象
DeleteIndexRequest request = new DeleteIndexRequest("hotel");
// 2.发送请求
client.indices().delete(request, RequestOptions.DEFAULT);
}
索引库是否存在
判断索引库是否存在,本质就是查询,对应的DSL是:
GET /hotel
@Test
void testExistsHotelIndex() throws IOException {
// 1.创建Request对象
GetIndexRequest request = new GetIndexRequest("hotel");
// 2.发送请求
boolean exists = client.indices().exists(request, RequestOptions.DEFAULT);
// 3.输出
System.err.println(exists ? "索引库已经存在!" : "索引库不存在!");
}
创建文档
创建文档的DSL语句如下:
POST /{索引库名}/_doc/1
{
"name": "Jack",
"age": 21
}
code:
@Data
@NoArgsConstructor
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();
}
}
@Test
void addHotelDoc() throws IOException {
Hotel hotel = hotelService.getById(36934L);
HotelDoc doc= new HotelDoc(hotel);
String json = JSON.toJSONString(doc);
IndexRequest request = new IndexRequest("hotel").id(hotel.getId().toString());
request.source(json, XContentType.JSON);
client.index(request, RequestOptions.DEFAULT);
}
Hotel在数据库中经纬度字段是分开的,es中经纬度是一个字段location,geo_point,所以用HotelDoc和es的属性做对应
删除文档
删除的DSL为是这样的:
DELETE /hotel/_doc/{id}
@Test
void testDeleteDocument() throws IOException {
// 1.准备Request
DeleteRequest request = new DeleteRequest("hotel", "61083");
// 2.发送请求
client.delete(request, RequestOptions.DEFAULT);
}
修改文档
修改我们讲过两种方式:
-
全量修改:本质是先根据id删除,再新增
-
增量修改:修改文档中的指定字段值
在RestClient的API中,全量修改与新增的API完全一致,判断依据是ID:
-
如果新增时,ID已经存在,则修改
-
如果新增时,ID不存在,则新增
这里是增量修改
@Test
void updateHotelDoc() throws IOException {
UpdateRequest request = new UpdateRequest("hotel","36934");
request.doc(
"price", 333,
"starName","三钻"
);
client.update(request, RequestOptions.DEFAULT);
}
批量操作文档
批量处理BulkRequest,其本质就是将多个普通的CRUD请求组合在一起发送。
其中提供了一个add方法,用来添加其他请求:
可以看到,能添加的请求包括:
-
IndexRequest,也就是新增
-
UpdateRequest,也就是修改
-
DeleteRequest,也就是删除
因此Bulk中添加了多个IndexRequest,就是批量新增功能了。:
@Test
void bulkHotelDoc() throws IOException {
List<Hotel> list = hotelService.list();
BulkRequest request = new BulkRequest();
for (Hotel hotel : list) {
HotelDoc doc = new HotelDoc(hotel);
request.add(
new IndexRequest("hotel")
.id(doc.getId().toString())
.source(JSON.toJSONString(doc), XContentType.JSON));
}
client.bulk(request, RequestOptions.DEFAULT);
}
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.解析响应
handleResponse(response);
}
private void handleResponse(SearchResponse response) {
// 4.解析响应
SearchHits searchHits = response.getHits();
// 4.1.获取总条数
long total = searchHits.getTotalHits().value;
System.out.println("共搜索到" + total + "条数据");
// 4.2.文档数组
SearchHit[] hits = searchHits.getHits();
// 4.3.遍历
for (SearchHit hit : hits) {
// 获取文档source
String json = hit.getSourceAsString();
// 反序列化
HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);
System.out.println("hotelDoc = " + hotelDoc);
}
}
match查询
@Test
void searchMatch() throws IOException {
SearchRequest request = new SearchRequest("hotel");
request.source().query(QueryBuilders.matchQuery("all", "如家"));
SearchResponse searchResponse = client.search(request, RequestOptions.DEFAULT);
printSearchResponse(searchResponse);
}
@Test
void searchMatchMulti() throws IOException {
SearchRequest request = new SearchRequest("hotel");
request.source().query(QueryBuilders.multiMatchQuery("外滩", "business", "name"));
SearchResponse searchResponse = client.search(request, RequestOptions.DEFAULT);
printSearchResponse(searchResponse);
}
term(精准查询)
-
term:词条精确匹配
@Test
void searchTerm() throws IOException {
SearchRequest request = new SearchRequest("hotel");
request.source().query(QueryBuilders.termQuery("city", "上海"));
SearchResponse searchResponse = client.search(request, RequestOptions.DEFAULT);
printSearchResponse(searchResponse);
}
range(范围查询)
@Test
void searchRange() throws IOException {
SearchRequest request = new SearchRequest("hotel");
request.source().query(QueryBuilders.rangeQuery("price").lte(200));
SearchResponse searchResponse = client.search(request, RequestOptions.DEFAULT);
printSearchResponse(searchResponse);
}
geo(坐标查询)
@Test
void searchGeo() throws IOException {
SearchRequest request = new SearchRequest("hotel");
request.source().query(
QueryBuilders.geoDistanceQuery("location")
.point(31.21, 121.5)
.distance(15, DistanceUnit.KILOMETERS));
SearchResponse searchResponse = client.search(request, RequestOptions.DEFAULT);
printSearchResponse(searchResponse);
}
functionScore(算分函数)
@Test
void searchFunctionScore() throws IOException {
SearchRequest request = new SearchRequest("hotel");
QueryBuilder queryBuilder = QueryBuilders.matchQuery("all", "外滩");
FunctionScoreQueryBuilder.FilterFunctionBuilder[] filterFunctionBuilders = new FunctionScoreQueryBuilder.FilterFunctionBuilder[1];
ScoreFunctionBuilder<WeightBuilder> scoreFunctionBuilder = new WeightBuilder();
scoreFunctionBuilder.setWeight(10);
QueryBuilder termQuery = QueryBuilders.termQuery("brand", "如家");
FunctionScoreQueryBuilder.FilterFunctionBuilder category = new FunctionScoreQueryBuilder.FilterFunctionBuilder(termQuery, scoreFunctionBuilder);
filterFunctionBuilders[0] = category;
request.source().query(
QueryBuilders.functionScoreQuery(queryBuilder,
filterFunctionBuilders)
// .scoreMode(FunctionScoreQuery.ScoreMode.SUM)
.boostMode(CombineFunction.SUM)
);
SearchResponse searchResponse = client.search(request, RequestOptions.DEFAULT);
printSearchResponse(searchResponse);
}
或者:
bool 查询 搭配 算分函数
// 1.构建BooleanQuery
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
// 关键字搜索
String key = params.getKey();
if (key == null || "".equals(key)) {
boolQuery.must(QueryBuilders.matchAllQuery());
} else {
boolQuery.must(QueryBuilders.matchQuery("all", key));
}
// 城市条件
if (params.getCity() != null && !params.getCity().equals("")) {
boolQuery.filter(QueryBuilders.termQuery("city", params.getCity()));
}
// 品牌条件
if (params.getBrand() != null && !params.getBrand().equals("")) {
boolQuery.filter(QueryBuilders.termQuery("brand", params.getBrand()));
}
// 星级条件
if (params.getStarName() != null && !params.getStarName().equals("")) {
boolQuery.filter(QueryBuilders.termQuery("starName", params.getStarName()));
}
// 价格
if (params.getMinPrice() != null && params.getMaxPrice() != null) {
boolQuery.filter(QueryBuilders
.rangeQuery("price")
.gte(params.getMinPrice())
.lte(params.getMaxPrice())
);
}
// 2.算分控制
FunctionScoreQueryBuilder functionScoreQuery =
QueryBuilders.functionScoreQuery(
// 原始查询,相关性算分的查询
boolQuery,
// function score的数组
new FunctionScoreQueryBuilder.FilterFunctionBuilder[]{
// 其中的一个function score 元素
new FunctionScoreQueryBuilder.FilterFunctionBuilder(
// 过滤条件
QueryBuilders.termQuery("isAD", true),
// 算分函数
ScoreFunctionBuilders.weightFactorFunction(10)
).boostMode(CombineFunction.SUM)
});
request.source().query(functionScoreQuery);
bool(布尔查询-多条件)
布尔查询是用must、must_not、filter等方式组合其它查询,代码示例如下:
可以看到,API与其它查询的差别同样是在查询条件的构建,QueryBuilders,结果解析等其他代码完全不变。
@Test
void searchBool() throws IOException {
SearchRequest request = new SearchRequest("hotel");
QueryBuilder mustBuilder = QueryBuilders.matchQuery("name", "如家");
QueryBuilder mustNotBuilder = QueryBuilders.rangeQuery("price").gt(400);
QueryBuilder geoBuilder = QueryBuilders.geoDistanceQuery("location")
.point(31.21, 121.5).distance(10, DistanceUnit.KILOMETERS);
request.source().query(
QueryBuilders.boolQuery()
.must(mustBuilder)
.mustNot(mustNotBuilder)
.filter(geoBuilder));
SearchResponse searchResponse = client.search(request, RequestOptions.DEFAULT);
printSearchResponse(searchResponse);
}
排序、分页
搜索结果的排序和分页是与query同级的参数,因此同样是使用request.source()来设置。
@Test
void testPageAndSort() throws IOException {
// 页码,每页大小
int page = 1, size = 5;
// 1.准备Request
SearchRequest request = new SearchRequest("hotel");
// 2.准备DSL
// 2.1.query
request.source().query(QueryBuilders.matchAllQuery());
// 2.2.排序 sort
request.source().sort("price", SortOrder.ASC);
// 2.3.分页 from、size
request.source().from((page - 1) * size).size(5);
// 3.发送请求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
// 4.解析响应
handleResponse(response);
}
距离排序
request.source().sort(SortBuilders
.geoDistanceSort("location", new GeoPoint(location))
.order(SortOrder.ASC)
.unit(DistanceUnit.KILOMETERS)
);
高亮
代码解读:
-
第一步:从结果中获取source。hit.getSourceAsString(),这部分是非高亮结果,json字符串。还需要反序列为HotelDoc对象
-
第二步:获取高亮结果。hit.getHighlightFields(),返回值是一个Map,key是高亮字段名称,值是HighlightField对象,代表高亮值
-
第三步:从map中根据高亮字段名称,获取高亮字段值对象HighlightField
-
第四步:从HighlightField中获取Fragments,并且转为字符串。这部分就是真正的高亮字符串了
-
第五步:用高亮的结果替换HotelDoc中的非高亮结果
private void handleResponse(SearchResponse response) {
// 4.解析响应
SearchHits searchHits = response.getHits();
// 4.1.获取总条数
long total = searchHits.getTotalHits().value;
System.out.println("共搜索到" + total + "条数据");
// 4.2.文档数组
SearchHit[] hits = searchHits.getHits();
// 4.3.遍历
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 = " + hotelDoc);
}
}
聚合
Bucket聚合(分组)
void searchAggs() throws IOException {
SearchRequest request = new SearchRequest("hotel");
//不返回文档数据
request.source().size(0);
request.source().aggregation(
AggregationBuilders
.terms("brandAgg")
.field("brand")
.size(20)
.order(BucketOrder.aggregation("_count",true))
);
SearchResponse searchResponse = client.search(request, RequestOptions.DEFAULT);
Aggregations aggregations = searchResponse.getAggregations();
Terms brandTerms = aggregations.get("brandAgg");
List<? extends Terms.Bucket> buckets = brandTerms.getBuckets();
for (Terms.Bucket bucket : buckets) {
System.out.println(bucket.getKeyAsString() + " : " + bucket.getDocCount());
}
}
获取多个聚合结果:
GET /hotel/_search
{
"size": 0,
"aggs": {
"brandAgg": {
"terms": {
"field": "brand",
"size": 2
}
},
"cityAgg": {
"terms": {
"field": "city",
"size": 2
}
}
}
}
request.source().aggregation(AggregationBuilders
.terms("brandAgg")
.field("brand")
.size(2)
);
request.source().aggregation(AggregationBuilders
.terms("cityAgg")
.field("city")
.size(2)
);
Metric聚合函数(min、max、avg)
现在我们需要对桶内的酒店做运算,获取每个品牌的用户评分的min、max、avg等值。
SearchRequest request = new SearchRequest("hotel");
request.source().size(0);
request.source().aggregation(
AggregationBuilders
.terms("brandAgg")
.field("brand")
.size(20)
.order(BucketOrder.aggregation("score_stats.avg",false))
.subAggregation(
AggregationBuilders
.stats("score_stats")
.field("score")
)
);
SearchResponse searchResponse = client.search(request, RequestOptions.DEFAULT);
结果:
@Test
void searchAggsMetric() throws IOException {
SearchRequest request = new SearchRequest("hotel");
request.source().size(0);
request.source().aggregation(
AggregationBuilders
.terms("brandAgg")
.field("brand")
.size(20)
.order(BucketOrder.aggregation("score_stats.avg",false))
.subAggregation(
AggregationBuilders
.stats("score_stats")
.field("score")
)
);
SearchResponse searchResponse = client.search(request, RequestOptions.DEFAULT);
Aggregations aggregations = searchResponse.getAggregations();
Terms brandTerms = aggregations.get("brandAgg");
List<? extends Terms.Bucket> buckets = brandTerms.getBuckets();
for (Terms.Bucket bucket : buckets) {
Aggregations bucketAggs = bucket.getAggregations();
Stats stats = bucketAggs.get("score_stats");
System.out.println(bucket.getKeyAsString() +" : " + JSON.toJSONString(stats));
}
}
词条(汉字 & 拼音)自动补全
目标根据酒店品牌 和 商圈的关键字,或者拼音,补全关联到的内容。
此处以(keyword)精准词语为例
1. 支持拼音关联搜索,则需要安装拼音分词器。
拼音分词器的安装请参考:elasticsearch 安装(docker)_naki_bb的博客-CSDN博客
不需要拼音补全的课参考 elasticsearch DSL命令_naki_bb的博客-CSDN博客的 自动补全
2.词条补全必须要有一个字段类型为suggestion。
索引库DSL语句如下
// 酒店数据索引库
PUT /hotel
{
"settings": {
"analysis": {
"analyzer": {
"text_anlyzer": {
"tokenizer": "ik_max_word",
"filter": "py"
},
"completion_analyzer": {
"tokenizer": "keyword",
"filter": "py"
}
},
"filter": {
"py": {
"type": "pinyin",
"keep_full_pinyin": false,
"keep_joined_full_pinyin": true,
"keep_original": true,
"limit_first_letter_length": 16,
"remove_duplicated_term": true,
"none_chinese_pinyin_tokenize": false
}
}
}
},
"mappings": {
"properties": {
"id":{
"type": "keyword"
},
"name":{
"type": "text",
"analyzer": "text_anlyzer",
"search_analyzer": "ik_smart",
"copy_to": "all"
},
"address":{
"type": "keyword",
"index": false
},
"price":{
"type": "integer"
},
"score":{
"type": "integer"
},
"brand":{
"type": "keyword",
"copy_to": "all"
},
"city":{
"type": "keyword"
},
"starName":{
"type": "keyword"
},
"business":{
"type": "keyword",
"copy_to": "all"
},
"location":{
"type": "geo_point"
},
"pic":{
"type": "keyword",
"index": false
},
"all":{
"type": "text",
"analyzer": "text_anlyzer",
"search_analyzer": "ik_smart"
},
"suggestion":{
"type": "completion",
"analyzer": "completion_analyzer"
}
}
}
}
实体类:
HotelDoc中要添加一个字段,用来做自动补全,内容可以是酒店品牌、商圈等信息。按照自动补全字段的要求,最好是这些字段的数组。
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
@Data
@NoArgsConstructor
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;
private Object distance;
private Boolean isAD;
private List<String> suggestion;
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();
// 组装suggestion
if(this.business.contains("/")){
// business有多个值,需要切割
String[] arr = this.business.split("/");
// 添加元素
this.suggestion = new ArrayList<>();
this.suggestion.add(this.brand);
Collections.addAll(this.suggestion, arr);
}else {
this.suggestion = Arrays.asList(this.brand, this.business);
}
}
}
重新导入数据功能,可以看到新的酒店数据中包含了suggestion:
API 示例
结果解析示例:
案例的实现如下:
@Override
public List<String> getSuggestions(String prefix) {
try {
// 1.准备Request
SearchRequest request = new SearchRequest("hotel");
// 2.准备DSL
request.source().suggest(new SuggestBuilder().addSuggestion(
"suggestions",
SuggestBuilders.completionSuggestion("suggestion")
.prefix(prefix)
.skipDuplicates(true)
.size(10)
));
// 3.发起请求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
// 4.解析结果
Suggest suggest = response.getSuggest();
// 4.1.根据补全查询名称,获取补全结果
CompletionSuggestion suggestions = suggest.getSuggestion("suggestions");
// 4.2.获取options
List<CompletionSuggestion.Entry.Option> options = suggestions.getOptions();
// 4.3.遍历
List<String> list = new ArrayList<>(options.size());
for (CompletionSuggestion.Entry.Option option : options) {
String text = option.getText().toString();
list.add(text);
}
return list;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
SpringBoot 整合 EleasticSearch 8.0.X 弃用high-level-client
请参考