微服务:java操作DSL
分页
elasticsearch支持对搜索结果排序,默认是根据相关度算分(_score)来排序。可以排序字段类型有:keyword类型、数值类型、地理坐标类型、日期类型等。
一旦自己指定排序,则_score排序就会失效。
基本语法
//基本语法
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" // 排序的距离单位
}
}
]
}
举例1
//对酒店数据按照用户评价降序排序,评价相同的按照价格升序排序
GET hotel/_search
{
"query": {
"match_all": {}
},
"sort": [
{
"score": {
"order": "desc"
},
"price": {
"order": "asc"
}
}
]
}
举例2
//实现对酒店数据按照到你的位置坐标的距离升序排序
GET hotel/_search
{
"query": {
"match_all": {}
},
"sort": [
{
"_geo_distance": {
"location": "31.220872,121.467579",
"order": "desc",
"unit": "km"
}
}
]
}
分页
elasticsearch 默认情况下只返回top10的数据。而如果要查询更多数据就需要修改分页参数了。elasticsearch中通过修改from、size参数来控制要返回的分页结果:
- from:从第几个文档开始
- size:总共查询几个文档
类似于mysql中的limit ?, ?
基本语法
GET /hotel/_search
{
"query": {
"match_all": {}
},
"from": 0, // 分页开始的位置,默认为0
"size": 10, // 期望获取的文档总数
"sort": [
{"price": "asc"}
]
}
深度分页问题
ES是分布式的,所以会面临深度分页问题。例如按price排序后,获取from = 990,size =10的数据:
-
首先在每个数据分片上都排序并查询前1000条文档。
-
然后将所有节点的结果聚合,在内存中重新排序选出前1000条文档
-
最后从这1000条中,选取从990开始的10条文档
如果搜索页数过深,或者结果集(from + size)越大,对内存和CPU的消耗也越高。因此ES设定结果集查询的上限是10000
深度分页解决方案
针对深度分页,ES提供了两种解决方案,官方文档:
-
search after:分页时需要排序,原理是从上一次的排序值开始,查询下一页数据。官方推荐使用的方式。
-
scroll:原理将排序数据形成快照,保存在内存。官方已经不推荐使用。
小结
分页查询的常见实现方案以及优缺点:
-
from + size
:- 优点:支持随机翻页
- 缺点:深度分页问题,默认查询上限(from + size)是10000
- 场景:百度、京东、谷歌、淘宝这样的随机翻页搜索
-
after search
:- 优点:没有查询上限(单次查询的size不超过10000)
- 缺点:只能向后逐页查询,不支持随机翻页
- 场景:没有随机翻页需求的搜索,例如手机向下滚动翻页
-
scroll
:- 优点:没有查询上限(单次查询的size不超过10000)
- 缺点:会有额外内存消耗,并且搜索结果是非实时的
- 场景:海量数据的获取和迁移。从ES7.1开始不推荐,建议用 after search方案。
高亮
高亮:就是在搜索结果中把搜索关键字突出显示。
原理是这样的:
-
将搜索结果中的关键字用标签标记出来
-
在页面中给标签添加css样式
基本语法
GET /hotel/_search
{
"query": {
"match": {
"FIELD": "TEXT" // 查询条件,高亮一定要使用全文检索查询
}
},
"highlight": {
"fields": { // 指定要高亮的字段
"FIELD": {
"pre_tags": "<em>", // 用来标记高亮字段的前置标签
"post_tags": "</em>" // 用来标记高亮字段的后置标签
}
}
}
}
注意:
- 高亮是对关键字高亮,因此搜索条件必须带有关键字,而不能是范围这样的查询。
- 默认情况下,高亮的字段,必须与搜索指定的字段一致,否则无法高亮
- 如果要对非搜索字段高亮,则需要添加一个属性:required_field_match=false
案例
//给所有如家高亮
GET hotel/_search
{
"query": {
"match": {
"all": "如家"
}
},
"highlight": {
"fields": {
"name": {
"pre_tags": "<em>",//前缀
"post_tags": "</em>",//后缀
"require_field_match": "false"//是否与查询字段统一
}
}
}
}
RestClient查询文档
快速入门查询所有(Match_all)
/**
* 这个方法是match_all的方法,并将返回的结果封装成java对象
* @throws IOException
*/
@Test
void matchAll() throws IOException {
//1.创建Request对象
SearchRequest request = new SearchRequest("hotel");
//2.准备请求参数:DSL语句
request.source().query(QueryBuilders.matchAllQuery());
//3.发送请求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
System.out.println(response);
}
返回的结果是一个SearchResponse,这是一个json风格的对象。
现在需要解析SearchResponse,对解析的方法进行抽取
/**
* 这个方法用来将SearchResponse进行解析
* @param response
* @return:泛型为HotelDoc的List集合
*/
private static List<HotelDoc> extracted(SearchResponse response) {
List<HotelDoc> list = new ArrayList<HotelDoc>();
SearchHits searchHits = response.getHits();
//4.1获取总条数
long total = searchHits.getTotalHits().value;
System.out.println("总搜索到"+total+"条记录");
//4.2获取文档数组
SearchHit[] hits = searchHits.getHits();
for (SearchHit hit : hits) {
String json = hit.getSourceAsString();
//4.3反序列化
HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);
list.add(hotelDoc);
}
return list;
}
代码解读:
-
第一步,创建
SearchRequest
对象,指定索引库名 -
第二步,利用
request.source()
构建DSL,DSL中可以包含查询、分页、排序、高亮等query()
:代表查询条件,利用QueryBuilders.matchAllQuery()
构建一个match_all查询的DSL
-
第三步,利用client.search()发送请求,得到响应
这里关键的API有两个,一个是request.source()
,其中包含了查询、排序、分页、高亮等所有功能:
另一个是QueryBuilders
,其中包含match、term、function_score、bool等各种查询:
全文检索查询Match
/**
* 这个方法是match的方法,并将返回的结果封装成java对象
* @throws IOException
*/
@Test
void match() throws IOException {
//1.创建Request对象
SearchRequest request = new SearchRequest("hotel");
//2.准备请求参数:DSL语句
request.source().query(QueryBuilders.matchQuery("name","如家"));
//3.发送请求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
}
全文搜索multi_match
/**
* 这个方法是multi_match的方法,并将返回的结果封装成java对象
* @throws IOException
*/
@Test
void multiMatch() throws IOException {
//1.创建Request对象
SearchRequest request = new SearchRequest("hotel");
//2.准备请求参数:DSL语句
request.source().query(QueryBuilders.multiMatchQuery("如家","name","bussiness"));
//3.发送请求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
for(HotelDoc hotelDoc :extracted(response)){
System.out.println(hotelDoc);
}
}
复合查询
/**
* 这个方法是bool的方法,并将返回的结果封装成java对象
* @throws IOException
*/
@Test
void bool() throws IOException {
//1.创建Request对象
SearchRequest request = new SearchRequest("hotel");
//2.1准备boolQuery
BoolQueryBuilder bool = QueryBuilders.boolQuery();
bool
.must(QueryBuilders.termQuery("city","上海"))
.filter(QueryBuilders.rangeQuery("price").gte(100).lte(500))
.must(QueryBuilders.termQuery("starName","四钻"));
//2.准备请求参数
request.source().query(bool);
//3.发送请求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
for(HotelDoc hotelDoc :extracted(response)){
System.out.println(hotelDoc);
}
}
分页、排序
/**
* 这个方法是match_all的方法,并将返回的结果封装成java对象
* 并实现了分页和排序
* @throws IOException
*/
@Test
void matchAll() throws IOException {
int page=1,size=5;
//1.创建Request对象
SearchRequest request = new SearchRequest("hotel");
//2.准备请求参数:DSL语句
request.source().query(QueryBuilders.matchAllQuery());
//3.1分页
request.source().from((page-1)*size).size(size);
//3.2排序
request.source().sort("price",SortOrder.DESC);
//3.发送请求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
extracted(response);
}
高亮
//4.2获取文档数组
SearchHit[] hits = searchHits.getHits();
for (SearchHit hit : hits) {
String json = hit.getSourceAsString();
//4.3反序列化
HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);
//4.4处理高亮
Map<String, HighlightField> highlightFields = hit.getHighlightFields();
HighlightField highlightField = highlightFields.get("name");
//4.4.1如果存在高亮则。。。
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);
list.add(hotelDoc);
}
-
所有搜索DSL的构建,记住一个API:SearchRequest的source()方法。
-
高亮结果解析是参考JSON结果,逐层解析