【搜尋系列】ES查詢常用例項演示

語言: CN / TW / HK

本文將作為es系列第三篇,結合常見的例項,來演示下如何通過 RestHighLevelClient 來實現es的各種查詢支援

I. 專案搭建

1. 專案依賴

本專案藉助 SpringBoot 2.2.1.RELEASE + maven 3.5.3 + IDEA 進行開發

開一個web服務用於測試

<dependencies>
    <dependency>
        <groupId>org.elasticsearch.client</groupId>
        <artifactId>elasticsearch-rest-high-level-client</artifactId>
    </dependency>
</dependencies>

2. 配置資訊

配置檔案application.yml,注意下面的配置資訊,下面採用的是由我們自己來解析配置的方式

elasticsearch:
  host: localhost
  port: 9200
  user: elastic
  pwd: test123
  connTimeout: 3000
  socketTimeout: 5000
  connectionRequestTimeout: 500

II. 例項演示

0. 準備

在開始之前,先準備插入幾條資料,這裡會藉助上一篇CURD博文中的插入介面

在開始之前就準備兩條資料

@Component
public class TermQueryDemo {
    private BasicCurdDemo basicCurdDemo;
    @Autowired
    private RestHighLevelClient client;
    @Autowired
    private RequestOptions requestOptions;

    private String TEST_ID = "11123-33345-66543-55231";
    private String TEST_ID_2 = "11123-33345-66543-55232";

    private String index = "term-demo";

    public TermQueryDemo(BasicCurdDemo basicCurdDemo) throws IOException {
        this.basicCurdDemo = basicCurdDemo;
        Map<String, Object> doc = newMap("name", "一灰灰", "age", 10, "skills", Arrays.asList("java", "python"), "site", "blog.hhui.top");
        basicCurdDemo.addDoc(index, doc, TEST_ID);
        doc = newMap("name", "二灰灰", "age", 16, "skills", Arrays.asList("js", "html"));
        basicCurdDemo.addDoc(index, doc, TEST_ID_2);
    }

    @PreDestroy
    public void remove() throws IOException {
        basicCurdDemo.delete(index, TEST_ID);
        basicCurdDemo.delete(index, TEST_ID_2);
    }
}

1. 全量查詢

即查詢所有的文件,如藉助kibanan的控制檯,發起的請求形如

GET index/_search
{
  "query": {
    "match_all": {}
  }
}

於此對應的java實現如下

/**
 * 全量查詢
 *
 * @throws IOException
 */
private void queryAll() throws IOException {
    SearchRequest searchRequest = new SearchRequest(index);
    searchRequest.types("_doc");

    SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
    // 查詢所有的文件
    searchSourceBuilder.query(QueryBuilders.matchAllQuery());
    searchRequest.source(searchSourceBuilder);

    SearchResponse searchResponse = client.search(searchRequest, requestOptions);
    System.out.println("mathAll: " + searchResponse.toString());
}

注意上面的實現:

SearchRequest
SearchSourceBuilder
client.search(searchRequest, requestOptions)

通常來說,實際的業務場景中,不太可能出現上面這種沒有任何限制的查全量資料,即便真的有查全量資料的case,更常見的是分頁查詢,如下

private void queryAll() throws IOException {
    SearchRequest searchRequest = new SearchRequest(index);
    searchRequest.types("_doc");

    SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
    int page = 1;
    //每頁記錄數
    int size = 2;
    //計算出記錄起始下標
    int from = (page - 1) * size;
    //起始記錄下標,從0開始
    searchSourceBuilder.from(from);
    //每頁顯示的記錄數
    searchSourceBuilder.size(size);
    // 根據age欄位進行倒排
    searchSourceBuilder.sort(new FieldSortBuilder("age").order(SortOrder.DESC));
    // 查詢所有的文件
    searchSourceBuilder.query(QueryBuilders.matchAllQuery());
    searchRequest.source(searchSourceBuilder);

    SearchResponse searchResponse = client.search(searchRequest, requestOptions);
    System.out.println("mathAll: " + searchResponse.toString());
}

2. 根據Field值精確查詢

即es中常說的term查詢,具體實現如下

/**
 * term精確查詢
 *
 * @throws IOException
 */
private void term() throws IOException {
    SearchRequest searchRequest = new SearchRequest(index);
    searchRequest.types("_doc");

    // termQuery: 精確查詢
    // SpanTermQuery: 詞距查詢
    SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
    searchSourceBuilder.query(QueryBuilders.termQuery("site", "blog.hhui.top"));
    searchRequest.source(searchSourceBuilder);
    SearchResponse response = client.search(searchRequest, requestOptions);
    System.out.println("term: " + response.toString());
}

從上面的實現也可以看出,查詢的套路沒啥區別,無非就是 SearchSourceBuilder 中的引數構造不一樣;上面主要通過

  • QueryBuilders.termQuery("site", "blog.hhui.top") 來構建 term的查詢條件,表明查詢 site=blog.hhui.top 的文件

中文查詢不到問題

在我們實際使用過程中,如果value為中文,在查詢時,可能會遇到命名有對應的資料,但是就查不到,主要原因就在於分詞,如對於中文的查詢,可以考慮下面這種方式

private void term2() throws IOException {
    SearchRequest searchRequest = new SearchRequest(index);
    searchRequest.types("_doc");

    SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
    // 對於中文查詢,需要注意分詞的場景, 如果直接使用 "name : 一灰灰" 的方式進行查詢,則啥也不會返回
    // elasticsearch 裡預設的IK分詞器是會將每一箇中文都進行了分詞的切割,所以你直接想查一整個詞,或者一整句話是無返回結果的。
    // 在此種情況下,我們可以通過指定 keyword 的方式來處理, 設定關鍵詞搜尋(不進行分詞)
    searchSourceBuilder.query(QueryBuilders.termQuery("name.keyword", "一灰灰"));

    searchRequest.source(searchSourceBuilder);
    SearchResponse response = client.search(searchRequest, requestOptions);
    System.out.println("term2: " + response.toString());
}

3. Field值in查詢

另外一個常見的就是多值查詢,也就是我們常說的 field in (val1, val2...) ,這個對應的就是es中的 terms 查詢

/**
 * 相當於in查詢
 * {"terms": { "name": ["一灰灰", "二灰灰] }}
 *
 * @throws IOException
 */
private void multTerm() throws IOException {
    SearchRequest searchRequest = new SearchRequest(index);
    searchRequest.types("_doc");

    SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
    searchSourceBuilder.query(QueryBuilders.termsQuery("name.keyword", "一灰灰", "二灰灰"));

    searchRequest.source(searchSourceBuilder);
    SearchResponse response = client.search(searchRequest, requestOptions);
    System.out.println("term: " + response.toString());
}

4. 範圍查詢

對於數值型別的Field,同樣是支援比較、範圍查詢的,對應的是es中 range

/**
 * 範圍查詢
 * { "range": { "age": { "gt":8, "lt": 12 } }}
 *
 * @throws IOException
 */
private void range() throws IOException {
    SearchRequest searchRequest = new SearchRequest(index);
    searchRequest.types("_doc");

    SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
    searchSourceBuilder.query(QueryBuilders.rangeQuery("age").gt(8).lt(12));
    searchRequest.source(searchSourceBuilder);
    SearchResponse response = client.search(searchRequest, requestOptions);
    System.out.println("range: " + response.toString());
}

注意上面的查詢有條件

QueryBuilders.rangeQuery("age").gt(8).lt(12)
age > 8 && age < 12

5. Field是否存在查詢

es不同於mysql的在於它的field可以動態新增,當我們希望查詢包含某個欄位的文件時,可以考慮 exists

/**
 * 根據欄位是否存在查詢
 *
 * @throws IOException
 */
private void exists() throws IOException {
    SearchRequest searchRequest = new SearchRequest(index);
    searchRequest.types("_doc");

    SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
    searchSourceBuilder.query(QueryBuilders.existsQuery("site"));
    searchRequest.source(searchSourceBuilder);
    SearchResponse response = client.search(searchRequest, requestOptions);
    System.out.println("exists: " + response.toString());
}

6. 模糊查詢

es作為搜尋引擎,更常見的是模糊匹配,比如match查詢

/**
   * 根據欄位匹配查詢
   *
   * @throws IOException
   */
  private void match() throws IOException {
      SearchRequest searchRequest = new SearchRequest(index);
      searchRequest.types("_doc");

      SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
      searchSourceBuilder.query(QueryBuilders.matchQuery("name", "灰"));
      searchRequest.source(searchSourceBuilder);
      SearchResponse response = client.search(searchRequest, requestOptions);
      System.out.println("matchQuery: " + response.toString());
  }

多Field中進行查詢

/**
 * 多欄位中查詢
 *
 * @throws IOException
 */
private void multiMatch() throws IOException {
    SearchRequest searchRequest = new SearchRequest(index);
    searchRequest.types("_doc");

    SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
    searchSourceBuilder.query(QueryBuilders.multiMatchQuery("灰", "name", "site"));
    searchRequest.source(searchSourceBuilder);
    SearchResponse response = client.search(searchRequest, requestOptions);
    System.out.println("multiMatchQuery: " + response.toString());
}

在es的語法支援中,除了match,還有一個 wildcard ,可以使用 ? 來代指單字元, * 來代指0..n字元

/**
 * 模糊查詢 ? 單字元  * 0..n字元
 *
 * @throws IOException
 */
private void wild() throws IOException {
    SearchRequest searchRequest = new SearchRequest(index);
    searchRequest.types("_doc");

    SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
    searchSourceBuilder.query(QueryBuilders.wildcardQuery("site", "*top"));
    searchRequest.source(searchSourceBuilder);
    SearchResponse response = client.search(searchRequest, requestOptions);
    System.out.println("wildcard: " + response.toString());
}

7. 正則匹配

private void regexp() throws IOException {
    SearchRequest searchRequest = new SearchRequest(index);
    searchRequest.types("_doc");

    SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
    searchSourceBuilder.query(QueryBuilders.regexpQuery("site", ".*hhui.*"));
    searchRequest.source(searchSourceBuilder);
    SearchResponse response = client.search(searchRequest, requestOptions);
    System.out.println("regexpQuery: " + response.toString());
}

8. 字首查詢

private void prefix() throws IOException {
    SearchRequest searchRequest = new SearchRequest(index);
    searchRequest.types("_doc");

    SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
    searchSourceBuilder.query(QueryBuilders.prefixQuery("site", "blog"));
    searchRequest.source(searchSourceBuilder);
    SearchResponse response = client.search(searchRequest, requestOptions);
    System.out.println("prefixQuery: " + response.toString());
}

9.小結

本文雖然介紹了一些常見的查詢case,但注意並不僅僅只有這些,比如

  • 查詢指定Feild的內容
  • 排序
  • 分組聚合
  • 多查詢條件組合:and/or
  • 高亮

更多的使用例項,敬請期待…,歡迎感興趣的小夥伴,點贊收藏評論一波:stuck_out_tongue_closed_eyes:

III. 不能錯過的原始碼和相關知識點

0. 專案

系列博文

原始碼

1. 微信公眾號: 一灰灰Blog

盡信書則不如,以上內容,純屬一家之言,因個人能力有限,難免有疏漏和錯誤之處,如發現bug或者有更好的建議,歡迎批評指正,不吝感激

下面一灰灰的個人部落格,記錄所有學習和工作中的博文,歡迎大家前去逛逛

打賞 如果覺得我的文章對您有幫助,請隨意打賞。