微服務: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結果,逐層解析


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2026 CODEPRJ.COM