概念
倒排索引
想象一下mysql正序索引,从id出发,由B+树作为结构组织索引,最后查到某条记录(文档)
而倒排索引是由文档开始检索,倒序找到的id,符合关键字查询的设计
分词器ik
es自带的standard分词不智能,推荐使用ik分词器
IK分词器包含两种模式:
-
ik_smart
:最少切分 -
ik_max_word
:最细切分
# 测试分词
POST /_analyze
{
"text":"好好学习,天天向上学java",
"analyzer": "standard"
}
创建mapping
mapping
——索引的约束,表结构
doc
——文档,一条记录
# 创建索引库(索引约束)
PUT /lan
{
"mappings": {
"properties": {
"info": {
"type": "text",
"analyzer": "ik_smart"
},
"email": {
"type": "keyword",
"index": false
},
"name": {
"type": "object",
"properties": {
"firstName": {
"type": "keyword"
},
"lastName": {
"type": "keyword"
}
}
}
}
}
}
查看索引,修改,删除
# 查看索引
GET /lan/_mapping
# 修改索引库,添加新字段
PUT /lan/_mapping
{
"properties":{
"age":{
"type":"long"
}
}
}
# 删除
DELETE /lan
添加文档,查看文档,修改文档,查看文档
不指定id,那么分配一个随机id
# 添加文档,指定了id为1,不指定则随机分配一串字符串
POST /lan/_doc/1
{
"info":"奥利给点赞大王",
"email":"agreeKing@aoligei.com",
"age":"11",
"name":{
"firstName":"无名",
"lastName":"陈"
}
}
# 全量修改-全量覆盖原来的文档,完全匹配,没有该id则是添加记录,少了字段相当于删除
PUT /lan/_doc/2
{
"info":"奥利给点赞大王",
"email":"agreeKing@aoligei.com",
"age":"10086"
}
# 增量修改,对该id下的某个字段尽行修改
POST /lan/_update/3
{
"doc": {
"name": {
"firstName": "无名",
"lastName": "张"
}
}
}
# 查看文档
GET /lan/_doc/1
RestClient
ES官方提供了各种不同语言的客户端,用来操作ES。这些客户端的本质就是组装DSL语句,通过http请求发送给ES。
java-RestClient官网:https://www.elastic.co/guide/en/elasticsearch/client/java-api-client/current/index.html
一、映射分析
创建索引库,最关键的是mapping映射,而mapping映射要考虑的信息包括:
- 字段名
- 字段数据类型
- 是否参与搜索
- 是否需要分词
- 如果分词,分词器是什么?
其中:
- 字段名、字段数据类型,可以参考数据表结构的名称和类型
- 是否参与搜索要分析业务来判断,例如图片地址,就无需参与搜索
- 是否分词呢要看内容,内容如果是一个整体就无需分词,反之则要分词
- 分词器,我们可以统一使用ik_max_word
# 酒店的mapping
PUT /hotel
{
"mappings": {
"properties": {
"id":{
"type": "keyword",
"index": false
},
"name":{
"type": "text",
"analyzer": "ik_max_word",
"copy_to": "all"
},
"address":{
"type":"keyword",
"index": false
},
"price":{
"type":"integer"
},
"sorce":{
"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”: “all”,逻辑上的视图,可将分词的文档逻辑上复制到该字段,方便检索,用户检索可通过这个字段
二、使用client创建索引等
构造请求,添加参数,发送请求
基本上所有api请求都是围绕着client.indices()
和restfulIndexRequest
创建RestClient
private RestHighLevelClient client;
@BeforeEach
void createClient() {
client = new RestHighLevelClient(
RestClient.builder(
HttpHost.create("http://192.168.234.129:9200")
));
}
创建索引
@Test
public void testCreateIndex() throws IOException {
// 1、构造请求
CreateIndexRequest request = new CreateIndexRequest("hotel");
// 2、添加参数,DSL语句
request.source(MAPPING_TEMPLATE, XContentType.JSON);
// 3、发送请求
client.indices().create(request, RequestOptions.DEFAULT);
}
删除索引,判断是否有该索引
@Test
public void testDeleteIndex() throws IOException {
String index = "hotel";
// 1、构造请求
DeleteIndexRequest deleteRequest = new DeleteIndexRequest(index);
// 2、判断是否有该索引
GetIndexRequest getIndexRequest = new GetIndexRequest(index);
boolean exists = client.indices().exists(getIndexRequest, RequestOptions.DEFAULT);
// 3、发送请求
if (exists){
client.indices().delete(deleteRequest, RequestOptions.DEFAULT);
}else {
System.out.println("索引:" + index + "不存在");
}
}
总结
JavaRestClient操作elasticsearch的流程基本类似。核心是client.indices()方法来获取索引库的操作对象。
索引库操作的基本步骤:
- 初始化RestHighLevelClient
- 创建XxxIndexRequest。XXX是Create、Get、Delete
- 准备DSL( Create时需要,其它是无参)
- 发送请求。调用RestHighLevelClient#indices().xxx()方法,xxx是create、exists、delete
三、使用client操作数据等
添加数据
@Test
public void testAddDocumentById() throws IOException {
String index = "hotel";
// 1、查找数据库
Hotel hotel = hotelService.getById(61075);
// 2、转换成es的数据,有可能数据库查出来的数据是需要处理的,这里处理的是坐标
HotelDoc hotelDoc = new HotelDoc(hotel);
// 3、转换成json字符创,构造请求,添加请求参数,顺便带上id,是数据库的id
IndexRequest request = new IndexRequest(index).id(hotel.getId().toString());
request.source(JSONUtil.toJsonStr(hotelDoc),XContentType.JSON);
client.index(request,RequestOptions.DEFAULT);
}
- 1)创建Request对象
- 2)准备请求参数,也就是DSL中的JSON文档
- 3)发送请求
变化的地方在于,这里直接使用client.xxx()的API,不再需要client.indices()了。
查找数据
@Test
public void testGetDocumentById() throws IOException {
String index = "hotel";
// 1、创建索引
GetRequest request = new GetRequest(index, "61075");
// 2、发送请求
GetResponse response = client.get(request, RequestOptions.DEFAULT);
// 3、获取
String sourceAsString = response.getSourceAsString();
HotelDoc hotelDoc = JSONUtil.toBean(sourceAsString, HotelDoc.class);
System.out.println(hotelDoc);
}
- 1)准备Request对象。这次是查询,所以是GetRequest
- 2)发送请求,得到结果。因为是查询,这里调用client.get()方法
- 3)解析结果,就是对JSON做反序列化
删除数据
@Test
void testDeleteDocument() throws IOException {
// 1.准备Request
DeleteRequest request = new DeleteRequest("hotel", "61083");
// 2.发送请求
client.delete(request, RequestOptions.DEFAULT);
}
- 1)准备Request对象,因为是删除,这次是DeleteRequest对象。要指定索引库名和id
- 2)准备参数,无参
- 3)发送请求。因为是删除,所以是client.delete()方法
修改数据
@Test
public void testUpdateDocumentById() throws IOException {
String index = "hotel";
// 1、创建索引
UpdateRequest request = new UpdateRequest(index, "61075");
// 增量修改
request.doc("price", "952",
"starName", "四钻");
// 2、发送请求
client.update(request, RequestOptions.DEFAULT);
}
有增量修改和全量修改,这里是增量修改
- 1)准备Request对象。这次是修改,所以是UpdateRequest
- 2)准备参数。也就是JSON文档,里面包含要修改的字段
- 3)更新文档。这里调用client.update()方法
bulk批量操作
@Test
public void testBulkDocumentById() throws IOException {
String index = "hotel";
// 1、查询数据库
List<Hotel> hotelList = hotelService.list();
// 2、构造请求
BulkRequest bulkRequest = new BulkRequest(index);
for (Hotel hotel:hotelList){
HotelDoc hotelDoc = new HotelDoc(hotel);
IndexRequest request = new IndexRequest(index).id(hotel.getId().toString());
request.source(JSONUtil.toJsonStr(hotelDoc),XContentType.JSON);
bulkRequest.add(request);
}
// 发送请求
client.bulk(bulkRequest,RequestOptions.DEFAULT);
}
总结
- 初始化RestHighLevelClient
- 创建XxxRequest。XXX是Index、Get、Update、Delete、Bulk
- 准备参数(Index、Update、Bulk时需要)
- 发送请求。调用RestHighLevelClient#.xxx()方法,xxx是index、get、update、delete、bulk
- 解析结果(Get时需要)
四、DSL查询语法
Elasticsearch提供了基于JSON的DSL(Domain Specific 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
4.1、查询的语法
GET /indexName/_search
{
"query": {
"查询类型": {
"查询条件": "条件值"
}
}
}
4.2、全文查找
GET /hotel/_search
{
"query": {
"match_all": {}
},
"size": 10
}
# 因为all逻辑上包含了copy_to的所有分词或keyword关键词,所以单找all是可以的
GET /hotel/_search
{
"query": {
"match": {
"all":"外滩如家"
}
},
"size": 10
}
# 多值查询,但是更推荐copy_to,因为多值查询性能不好
GET /hotel/_search
{
"query": {
"multi_match": {
"query": "外滩如家",
"fields": ["brand","name","city"]
}
},
"size": 10
}
总结
match和multi_match的区别是什么?
- match:根据一个字段查询
- multi_match:根据多个字段查询,参与查询字段越多,查询性能越差
4.3、精确查找
term:完全匹配,必须不分词
# 精确查询
GET /hotel/_search
{
"query": {
"term": {
"city":"深圳"
}
},
"size": 10
}
range:范围查找,一般对是数值类型
# 范围查找
GET /hotel/_search
{
"query": {
"range": {
"score":{
"gte": 40,
"lte": 60
}
}
},
"size": 10
}
总结
精确查询常见的有哪些?
- term查询:根据词条精确匹配,一般搜索keyword类型、数值类型、布尔类型、日期类型字段
- range查询:根据数值范围查询,可以是数值、日期的范围
4.4、地理位置查找
所谓的地理坐标查询,其实就是根据经纬度查询,官方文档:https://www.elastic.co/guide/en/elasticsearch/reference/current/geo-queries.html
常见的使用场景包括:
- 携程:搜索我附近的酒店
- 滴滴:搜索我附近的出租车
- 微信:搜索我附近的人
附近的酒店:
附近的车:
4.4.1.矩形范围查询
矩形范围查询,也就是geo_bounding_box查询,查询坐标落在某个矩形范围的所有文档:
查询时,需要指定矩形的左上、右下两个点的坐标,然后画出一个矩形,落在该矩形内的都是符合条件的点。
语法如下:
// geo_bounding_box查询
GET /indexName/_search
{
"query": {
"geo_bounding_box": {
"FIELD": {
"top_left": { // 左上点
"lat": 31.1,
"lon": 121.5
},
"bottom_right": { // 右下点
"lat": 30.9,
"lon": 121.7
}
}
}
}
}
这种并不符合“附近的人”这样的需求,所以我们就不做了。
4.4.2附近查询
附近查询,也叫做距离查询(geo_distance):查询到指定中心点小于某个距离值的所有文档。
换句话来说,在地图上找一个点作为圆心,以指定距离为半径,画一个圆,落在圆内的坐标都算符合条件:
语法说明:
// geo_distance 查询
GET /indexName/_search
{
"query": {
"geo_distance": {
"distance": "15km", // 半径
"FIELD": "31.21,121.5" // 圆心
}
}
}
示例:
我们先搜索陆家嘴附近15km的酒店:
发现共有47家酒店。
然后把半径缩短到3公里:
可以发现,搜索到的酒店数量减少到了5家。
4.5、复合查询
可以将简单的查询组合起来,实现一些复杂一点的逻辑
4.5.1、算分查询
相关性算分
当我们利用match查询时,文档结果会根据与搜索词条的关联度打分(_score),返回结果时按照分值降序排列。
例如,我们搜索 “虹桥如家”,结果如下:
[
{
"_score" : 17.850193,
"_source" : {
"name" : "虹桥如家酒店真不错",
}
},
{
"_score" : 12.259849,
"_source" : {
"name" : "外滩如家酒店真不错",
}
},
{
"_score" : 11.91091,
"_source" : {
"name" : "迪士尼如家酒店真不错",
}
}
]
在elasticsearch中,早期使用的打分算法是TF-IDF算法,公式如下:
在后来的5.1版本升级中,elasticsearch将算法改进为BM25算法,公式如下:
TF-IDF算法有一各缺陷,就是词条频率越高,文档得分也会越高,单个词条对文档影响较大。而BM25则会让单个词条的算分有一个上限,曲线更加平滑:
小结:elasticsearch会根据词条和文档的相关度做打分,算法由两种:
- TF-IDF算法
- BM25算法,elasticsearch5.1版本后采用的算法
当我们利用match查询时,文档结果会根据与搜索词条的关联度打分(_score),返回结果时按照分值降序排列
GET /hotel/_search
{
"query": {
"function_score": {
"query": {
"match": {
"all": "外滩"
}
},
"functions": [
{
"filter": {
"term": {
"brand": "如家"
}
},
"weight": 10
}
],
"boost_mode": "multiply"
}
}
}
4.5.2、布尔查询
bool查询有几种逻辑关系?
- must:必须匹配的条件,可以理解为“与”
- should:选择性匹配的条件,可以理解为“或”
- must_not:必须不匹配的条件,不参与打分
- filter:必须匹配的条件,不参与打分
差别在于算不算分,有些字段是不参与算分,不要用算分的条件,不然算分是会影响性能和结果的,像价格地理坐标
GET /hotel/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"all": "如家"
}
},
{
"geo_distance": {
"distance": "10km",
"location": {
"lat": 31.21,
"lon": 121.5
}
}
}
],
"must_not": [
{
"range": {
"price": {
"gte": 400
}
}
}
]
}
}
}
五、结果处理
5.1、排序
可以排序的字段类型有:keyword,数值,地理坐标,日期
基本语法
排序的sort和查询的query是同级的
普通排序
# 普通类型
GET /indexName/_search
{
"query": {
"match_all": {}
},
"sort": [
{
"FIELD": "desc" // 排序字段、排序方式ASC、DESC
}
]
地理坐标排序
# 地理坐标类型
GET /indexName/_search
{
"query": {
"match_all": {}
},
"sort": [
{
"_geo_distance" : {
"FIELD" : "纬度,经度", // 文档中geo_point类型的字段名、目标坐标点
"order" : "asc", // 排序方式
"unit" : "km" // 排序的距离单位
}
}
]
}
5.2、分页
elasticsearch 默认情况下只返回top10的数据。而如果要查询更多数据就需要修改分页参数了。elasticsearch中通过修改from、size参数来控制要返回的分页结果:
- from:从第几个文档开始
- size:总共查询几个文档
类似于mysql中的limit ?, ?
from和size是和query同级的
基本分页
GET /hotel/_search
{
"query": {
"match_all": {}
},
"from": 0, // 分页开始的位置,默认为0
"size": 10, // 期望获取的文档总数
"sort": [
{"price": "asc"}
]
}
深度分页
对于es来说,一定是会搭建集群的,到时候每个分片的数据都不一样
例如需要9900-10000,排序分页必须是每个分片先选好各自的9900-10000,再一起比较,选出其中的100条
显然这及其影响内存和性能,所以es限定最多分页10000条,超出则报错
对于深度分页,es有两种方案
- search after:分页时需要排序,原理是从上一次的排序值开始,查询下一页数据。官方推荐使用的方式。
- scroll:原理将排序后的文档id形成快照,保存在内存。官方已经不推荐使用。
5.3、高亮
把搜索关键字在查询结果高亮显示,一般是查询结果的文本会用到html标签或者css样式
高亮的语法:
GET /hotel/_search
{
"query": {
"match": {
"FIELD": "TEXT" // 查询条件,高亮一定要使用全文检索查询
}
},
"highlight": {
"fields": { // 指定要高亮的字段
"FIELD": {
"pre_tags": "<em>", // 用来标记高亮字段的前置标签
"post_tags": "</em>" // 用来标记高亮字段的后置标签
}
}
}
}
注意:
- 高亮是对关键字高亮,因此搜索条件必须带有关键字,而不能是范围这样的查询。
- 默认情况下,高亮的字段,必须与搜索指定的字段一致,否则无法高亮
- 如果要对非搜索字段高亮,则需要添加一个属性:required_field_match=false
示例:
总结
查询的DSL是一个大的JSON对象,包含下列属性:
- query:查询条件
- from和size:分页条件
- sort:排序条件
- highlight:高亮条件
示例:
六、java发起查询请求
想象source是DSL查询语句的外层大括号,那么之后的方法就是大括号里面的结构了,这里关键的API有两个,一个是request.source()
,其中包含了查询、排序、分页、高亮等所有功能:
另一个是QueryBuilders
,其中包含match、term、function_score、bool等各种查询:
/**
* 查询所有
*/
@Test
public void testMatchAll() throws IOException {
String index = "hotel";
// 1、创建查询请求
SearchRequest request = new SearchRequest(index);
// 2、添加查询参数
request.source().query(QueryBuilders.matchAllQuery());
// 3、发送查询请求
SearchResponse searchResponse = client.search(request, RequestOptions.DEFAULT);
// 解析响应结果
SearchHits hits = searchResponse.getHits();
long count = hits.getTotalHits().value;
// 遍历查询到的结果
for (SearchHit hit : hits.getHits()) {
// 转换json字符串成实体
String sourceAsString = hit.getSourceAsString();
HotelDoc hotelDoc = JSONUtil.toBean(sourceAsString, HotelDoc.class);
System.out.println(hotelDoc);
}
}
查询的基本步骤是:
-
创建SearchRequest对象
-
准备Request.source(),也就是DSL。
① QueryBuilders来构建查询条件
② 传入Request.source() 的 query() 方法
-
发送请求,得到结果
-
解析结果(参考JSON结果,从外到内,逐层解析)
match查询
private void handleSearchResponse(SearchResponse searchResponse) {
SearchHits hits = searchResponse.getHits();
long count = hits.getTotalHits().value;
// 遍历查询到的结果
for (SearchHit hit : hits.getHits()) {
// 转换json字符串成实体
String sourceAsString = hit.getSourceAsString();
HotelDoc hotelDoc = JSONUtil.toBean(sourceAsString, HotelDoc.class);
System.out.println(hotelDoc);
}
}
@Test
public void testMatchQuery() throws IOException {
String index = "hotel";
// 1、创建查询请求
SearchRequest request = new SearchRequest(index);
// 2、添加查询参数
MatchQueryBuilder matchQueryBuilder = QueryBuilders.matchQuery("all", "如家");
request.source().query(matchQueryBuilder);
// 3、发送请求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
// 4、解析结果
handleSearchResponse(response);
}
其他的精确匹配term
还是范围range
都差不多
分页
分页、排序和查询是同级的
request.source().query(matchQueryBuilder)
.from(0).size(10).sort("price",SortOrder.ASC);
高亮
高亮结果解析是更需要处理的,highlight和查询query是同级的
@Test
public void testMatchQuery() throws IOException {
String index = "hotel";
// 1、创建查询请求
SearchRequest request = new SearchRequest(index);
// 2、添加查询参数
MatchQueryBuilder matchQueryBuilder = QueryBuilders.matchQuery("all", "如家");
request.source().query(matchQueryBuilder).from(0).size(10).sort("price",SortOrder.ASC);
// 高亮
HighlightBuilder highlightBuilder = new HighlightBuilder();
highlightBuilder.field("name").requireFieldMatch(false);
highlightBuilder.field("brand").requireFieldMatch(false);
request.source().highlighter(highlightBuilder);
// 3、发送请求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
// 4、解析结果
handleSearchResponse(response);
}
/**
* 处理响应结果
*/
private void handleSearchResponse(SearchResponse searchResponse) {
SearchHits hits = searchResponse.getHits();
long count = hits.getTotalHits().value;
// 遍历查询到的结果
for (SearchHit hit : hits.getHits()) {
// 转换json字符串成实体
String sourceAsString = hit.getSourceAsString();
Map<String, HighlightField> highlightFieldMap = hit.getHighlightFields();
HotelDoc hotelDoc = JSONUtil.toBean(sourceAsString, HotelDoc.class);
if (CollectionUtil.isNotEmpty(highlightFieldMap)){
HighlightField nameField = highlightFieldMap.get("name");
if (nameField != null){
String name = nameField.getFragments()[0].toString();
hotelDoc.setName(name);
}
}
System.out.println(hotelDoc);
}
}
七、旅游案例
7.1、搜索、分页、地理位置距离排序,广告算分查询
处理以及发送请求,处理和query不同级的地理位置排序、分页
/**
* 处理以及发送请求,处理和query不同级的地理位置排序、分页
* @param requestParam
* @return
*/
@Override
public PageResult selectHotelList(RequestParams requestParam) {
try {
String index = "hotel";
// 1、创建请求
SearchRequest request = new SearchRequest(index);
// 2、添加参数
// 2.1、query
this.buildBasicQuery(requestParam,request);
// 2.2、分页
int page = requestParam.getPage();
int size = requestParam.getSize();
request.source().from((page - 1) * size).size(size);
// 2.3、地理位置排序,由近及远,升序
String location = requestParam.getLocation();
if (StrUtil.isNotBlank(location)) {
request.source().sort(SortBuilders
.geoDistanceSort("location", new GeoPoint(location))
.order(SortOrder.ASC)
.unit(DistanceUnit.KILOMETERS));
}
// 3、发送请求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
// 4、结果解析
return this.handleSearchResponse(response);
} catch (IOException e) {
throw new RuntimeException();
}
}
处理请求参数(品牌、城市、星级等)以及算分
/**
* 处理请求参数(品牌、城市、星级等)以及算分
* @param requestParam
* @param request
*/
private void buildBasicQuery(RequestParams requestParam, SearchRequest request) {
// 构建BooleanQuery
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
// 1、城市,不用算分
if (StrUtil.isNotBlank(requestParam.getCity())) {
boolQuery.filter(QueryBuilders.termQuery("city", requestParam.getCity()));
}
// 2、关键字
if (StrUtil.isNotBlank(requestParam.getKey())) {
boolQuery.filter(QueryBuilders.matchQuery("all", requestParam.getKey()));
} else {
boolQuery.filter(QueryBuilders.matchAllQuery());
}
// 3、品牌
if (StrUtil.isNotBlank(requestParam.getBrand())) {
boolQuery.filter(QueryBuilders.termQuery("brand", requestParam.getBrand()));
}
// 4、星级
if (StrUtil.isNotBlank(requestParam.getStarName())) {
boolQuery.filter(QueryBuilders.termQuery("starName", requestParam.getStarName()));
}
// 5、价格
if (requestParam.getMinPrice() != null && requestParam.getMaxPrice() != null) {
boolQuery.filter(QueryBuilders
.rangeQuery("price")
.gte(requestParam.getMinPrice())
.lte(requestParam.getMaxPrice()));
}
// 6、算分查询,这里判断是不是广告
FunctionScoreQueryBuilder functionScoreQueryBuilder = QueryBuilders.functionScoreQuery(
boolQuery, new FunctionScoreQueryBuilder.FilterFunctionBuilder[]{
new FunctionScoreQueryBuilder.FilterFunctionBuilder(
// 过滤条件
QueryBuilders.termQuery("isAD", true),
ScoreFunctionBuilders.weightFactorFunction(10)
)
}).boostMode(CombineFunction.MULTIPLY);
// 7、添加参数
request.source().query(functionScoreQueryBuilder);
}
处理查询到的数据,封装成分页的结果,处理地理位置距离排序,反序列化
/**
* 处理查询到的数据,封装成分页的结果,处理地理位置距离排序,反序列化
*
* @param searchResponse
* @return
*/
private PageResult handleSearchResponse(SearchResponse searchResponse) {
SearchHits searchHits = searchResponse.getHits();
long total = searchHits.getTotalHits().value;
// 遍历查询到的结果
SearchHit[] hits = searchHits.getHits();
List<HotelDoc> hotelDocList = new ArrayList<>(hits.length);
for (SearchHit hit : hits) {
// 转换json字符串成实体
String sourceAsString = hit.getSourceAsString();
HotelDoc hotelDoc = JSONUtil.toBean(sourceAsString, HotelDoc.class);
// 获取排序值,地理位置的排序值就是相差的距离
Object[] sortValues = hit.getSortValues();
if (sortValues.length > 0) {
hotelDoc.setDistance(sortValues[0]);
}
hotelDocList.add(hotelDoc);
}
return new PageResult(total, hotelDocList);
}
像高亮、排序等,有可能不只是只有一个字段,所以返回的是个数组,不同字段的下标不同