微服务: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的数据:

  1. 首先在每个数据分片上都排序并查询前1000条文档。

  2. 然后将所有节点的结果聚合,在内存中重新排序选出前1000条文档

  3. 最后从这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(),其中包含了查询、排序、分页、高亮等所有功能:

image-20210721215640790

另一个是QueryBuilders,其中包含match、term、function_score、bool等各种查询:

image-20210721215729236

全文检索查询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结果,逐层解析

posted @ 2022-03-30 23:17  Boerk  阅读(855)  评论(0编辑  收藏  举报