Elastic Search Java Api

前言

前文咱們提到過Elastic Search 操做索引的 Rest Api。實際上 Elastic Search 的 Rest Api 提供了全部的操做接口。在編程語言中能夠直接這麼使用 Rest Api 能夠調用 Elastic Search 的全部功能,可是很是的不方便和直觀,因此Elastic Search 官方也爲不少語言提供了訪問的 Api 接口。官方提供的編程語言接口包括:html

  • Java
  • JavaScript
  • Groovy
  • PHP
  • .NET
  • Perl
  • Python
  • Ruby

同時編程社區也提供了大量的編程語言的 Api。目前主要有java

  • B4J
  • Clojure
  • ColdFusion (CFML)
  • Erlang
  • Go
  • Groovy
  • Haskell
  • Java
  • JavaScript
  • kotlin
  • Lua
  • .NET
  • OCaml
  • Perl
  • PHP
  • Python
  • R
  • Ruby
  • Rust
  • Scala
  • Smalltalk
  • Vert.x

平時咱們都是用 Java 進行開發。因此這裏我會談談 Elastic Search 的 Java Api 的使用方式git

準備工做

爲了說明 Java Api 的功能,咱們準備了一個場景。在這裏咱們假定有一批做者,每一個做者都有標識、姓名、性別、年齡,描述着幾個字段。咱們須要經過姓名、年齡、描述中的關鍵詞來查詢做者,程序員

在這裏,程序主要經過 JUnit 測試用例的方式來運行,因此首先引入了 JUnit 的依賴github

<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
    <scope>test</scope>
</dependency>
複製代碼

Java Api 概述

Elastic Search 提供了官方的 Java Api。這裏包括兩類,一類是 Low Level Rest Api(低級 Rest Api)和 High Leve Rest Api(高級 Rest Api)。apache

所謂低級 Api 並非功能比較弱,而是指 Api 離底層實現比較近。官方提供的低級 Api 是對原始的 Rest Api 的第一層封裝。只是把 Http 調用的細節封裝起來。程序仍是要本身組裝查詢的條件字符串、解析返回的結果 json 字符串等。同時也要處理 http 協議的 各類方法、協議頭等內容。編程

高級 api 是在低級 api 上的進一步封裝,不用在在乎接口的方法,協議頭,也不用人工組合調用的參數字符串,同時對返回的 json 字符串有必定的解析。使用上更方便一些。可是高級 api 並無實現全部低級 api 實現的功能。因此若是遇到這種狀況,還須要利用低級 api 來實現本身功能。json

第三方 Java 客戶端是有社區本身開發的 Elastic Search 客戶端。官方提到了兩個開源在 GitHub 上的項目 FlummiJestapi

Java Low Level Rest Api 使用說明

低級 Api 的優點在於依賴的其餘庫很是少,並且功能完備。缺點在於封裝不夠高級,因此使用起來仍是很是的繁瑣。咱們這裏先來看看低級的 api 是怎麼使用的。bash

引入依賴

在前面創建的 Maven Java 工程中,要使用 Elastic Search 的低級 Api,首先要引入 低級 Api 的依賴。以下所示

<dependency>
    <groupId>org.elasticsearch.client</groupId>
    <artifactId>elasticsearch-rest-client</artifactId>
    <version>6.1.1</version>
</dependency>
複製代碼

創建客戶端

RestClient restClient = RestClient.builder(
        new HttpHost("localhost", 9200, "http"),
        new HttpHost("localhost", 9201, "http")).build();
複製代碼

咱們經過 RestClient 對象的靜態方法 builder(HttpHost... hosts) 和 builder()創建一個 Elastic Search 的 Rest 客戶端。其中 hosts 是一個可變參數,用來指定 Elastic Cluster 集羣的節點的 ip、端口、協議。

方法調用

創建了客戶端之後,經過兩類方法來調用 Rest Api。一類是同步調用,一類是異步調用。

同步調用

同步調用主要的方法聲明以下所示:

public Response performRequest(String method, String endpoint, Header... headers) throws IOException

public Response performRequest(String method, String endpoint, Map<String, String> params, Header... headers) throws IOException

public Response performRequest(String method, String endpoint, Map<String, String> params,
                                   HttpEntity entity, Header... headers) throws IOException

複製代碼

這是三個重載的方法,參數 method 表明的是 Rest Api 的方法,例如 PUT、GET、POST、DELETE等;參數 endpoint 表明的是 Rest Api 參數的地址,從 Rest Api 的 URL 的 ip:port 字段以後開始;params 是經過 url 參數形式傳遞的參數;entity 是經過 http body 傳遞的參數;headers 是一個可變參數,能夠傳入對應的 http 頭信息。

例如,我要查看一個索引 author_test 的信息,咱們能夠用以下的代碼來獲取

Response response = restClient.performRequest("GET", "/author_test");
複製代碼

再好比,咱們要查看一個索引 author_test 中 des 字段中包含軟件的文檔信息,咱們能夠用以下代碼來獲取:

String queryJson = "{\n" +
        " \"query\": {\n" +
        " \"match\": {\n" +
        " \"des\": \"軟件\"\n" +
        " }\n" +
        " }\n" +
        "}";
Response response = restClient.performRequest("POST",
        "/author_test/_search",
        new HashMap<String, String>(),
        new NStringEntity(queryJson,ContentType.APPLICATION_JSON));
複製代碼

異步調用

異步調用和同步調用的參數是同樣的,可是異步調用沒有返回值,而是在參數中有一個 ResponseListener 回調對象,在調用完成後自動調用。這個回調對象是一個接口,須要程序員本身來實現。

異步調用的方法聲明以下所示:

public void performRequestAsync(String method, String endpoint, ResponseListener responseListener, Header... headers)

public void performRequestAsync(String method, String endpoint, Map<String, String> params,
                                    ResponseListener responseListener, Header... headers)

public void performRequestAsync(String method, String endpoint, Map<String, String> params,
                                    HttpEntity entity, ResponseListener responseListener, Header... headers) 
複製代碼

例如,我要用異步調用的方式查詢 author_test 索引中 des 中包含 「軟件」 的全部文檔,則代碼實現以下

String queryJson = "{\n" +
        " \"query\": {\n" +
        " \"match\": {\n" +
        " \"des\": \"軟件\"\n" +
        " }\n" +
        " }\n" +
        "}";


restClient.performRequestAsync("POST",
        "/author_test/_search",
        new HashMap<String, String>(),
        new NStringEntity(queryJson, ContentType.APPLICATION_JSON), new ResponseListener() {
            public void onSuccess(Response response) {
                try {
                    String responseData = readResposne(response);
                    System.out.println("******* search success ******");
                    System.out.println(responseData);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }

            public void onFailure(Exception exception) {
                exception.printStackTrace();
            }
        });
複製代碼

Java High Level Rest Api 使用說明

Elastic Search 的 Java 高級 Api 相對低級 Api 來講,抽象程度更高一些。不過我我的以爲仍是挺難用的。並且高級 Api 並不支持全部的 Rest Api 的功能。官方有高級 Api 支持的功能列表。從這裏看,若是你只是作查詢,用高級 Api 接口仍是夠用的。

引入依賴

在前面創建的 Maven Java 工程中,要使用 Elastic Search 的低級 Api,首先要引入 低級 Api 的依賴。以下所示

<dependency>
    <groupId>org.elasticsearch.client</groupId>
    <artifactId>elasticsearch-rest-high-level-client</artifactId>
    <version>6.1.1</version>
</dependency>
複製代碼

創建客戶端

RestHighLevelClient client = new RestHighLevelClient(
        RestClient.builder(
                new HttpHost("localhost", 9200, "http"),
                new HttpHost("localhost", 9201, "http")));
複製代碼

和低級接口相似,先經過 RestClient 對象的靜態方法 builder(HttpHost... hosts)方法創建一個 RestClientBuilder 對象,而後做爲 RestHighLevelClient 對象構造函數的參數,來建立一個新的高級客戶端對象。其中 hosts 是一個可變參數,用來指定 Elastic Cluster 集羣的節點的 ip、端口、協議。

方法調用

這裏用高級接口來實現低級接口中第一個查詢的功能。代碼以下

SearchRequest searchRequest = new SearchRequest("author_test");
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
sourceBuilder.query(QueryBuilders.matchQuery("des", "軟件"));
sourceBuilder.from(0);
sourceBuilder.size(5);
searchRequest.source(sourceBuilder);
SearchResponse response = restClient.search(searchRequest);
複製代碼

其餘的接口的調用均可以查找對應的 api 文檔說明來完成

完整代碼

最後一個章節將完整的代碼貼出來。

初始化代碼

這部分代碼負責初始化測試的索引和索引文檔。須要注意一下,前面咱們說過 Elastic Search 是一個準實時的系統,因此索引完文檔後,若是立刻查詢,可能查詢不到數據,須要有一個小的延遲。

package com.x9710.es.test;

import org.apache.http.HttpEntity;
import org.apache.http.HttpHost;
import org.apache.http.entity.ContentType;
import org.apache.http.nio.entity.NStringEntity;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

public class IndexInitUtil {
    public RestClient initLowLevelClient() {
        // 經過 ip 、port 和協議創建 Elastic Search 客戶端
        RestClient restClient = RestClient.builder(
                new HttpHost("10.110.2.53", 9200, "http")).build();


        try {
            initIndex(restClient);

        } catch (Exception e) {
            e.printStackTrace();
        }
        return restClient;
    }

    public RestHighLevelClient initHighLevelClient() {
        // 經過 ip 、port 和協議創建 Elastic Search 客戶端
        RestHighLevelClient highLevelClient = new RestHighLevelClient(
                RestClient.builder(
                        new HttpHost("10.110.2.53", 9200, "http"))
        );

        RestClient restClient = RestClient.builder(
                new HttpHost("10.110.2.53", 9200, "http")).build();

        try {
            initIndex(restClient);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                restClient.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }


        return highLevelClient;
    }

    private void initIndex(RestClient restClient) {

        String authIndexDefine = "{\n" +
                "\t\"settings\" : {\n" +
                " \"index\" : {\n" +
                " \"number_of_shards\" : 6,\n" +
                " \"number_of_replicas\" : 0\n" +
                " }\n" +
                " },\n" +
                " \"mappings\": {\n" +
                " \"doc\": {\n" +
                " \"properties\": {\n" +
                " \t\"id\": {\"type\": \"text\"},\n" +
                " \"name\": {\"type\": \"text\"},\n" +
                " \"sex\": {\"type\": \"text\"},\n" +
                " \"age\": {\"type\": \"integer\"},\n" +
                " \"des\":{\n" +
                " \t\"type\":\"text\",\n" +
                " \t\"analyzer\": \"ik_max_word\",\n" +
                "\t\t\t\t\t\"search_analyzer\": \"ik_max_word\"\n" +
                " }\n" +
                " }\n" +
                " }\n" +
                " }\n" +
                "}";

        HttpEntity authorIndexEntity = new NStringEntity(authIndexDefine, ContentType.APPLICATION_JSON);
        //初始化要索引的 author 文檔列表
        List<HttpEntity> authorDocs = new ArrayList<HttpEntity>();
        authorDocs.add(new NStringEntity(" {\n" +
                "\t\"id\":\"A1001\",\n" +
                "\t\"name\":\"任盈盈\",\n" +
                "\t\"age\":24,\n" +
                "\t\"sex\":\"女\",\n" +
                "\t\"des\":\"IT軟件工程師,擅長Java和軟件架構\"\n" +
                " }", ContentType.APPLICATION_JSON));
        authorDocs.add(new NStringEntity(" {\n" +
                "\t\"id\":\"A1002\",\n" +
                "\t\"name\":\"風清揚\",\n" +
                "\t\"age\":47,\n" +
                "\t\"sex\":\"男\",\n" +
                "\t\"des\":\"IT軟件技術經理,擅長技術管理過程控制\"\n" +
                " }", ContentType.APPLICATION_JSON));


        try {
            //建立 author_test 索引
            restClient.performRequest("PUT", "/author_test", new HashMap<String, String>(), authorIndexEntity);

            //索引 author_index 文檔
            for (int i = 0; i < authorDocs.size(); i++) {
                restClient.performRequest("POST", "/author_test/doc", new HashMap<String, String>(), authorDocs.get(i));
            }
            //注意索引文檔完成後,作一個小的延遲,保證後續查詢能查到數據
            Thread.currentThread().sleep(1000);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

複製代碼

低級 Api 測試樣例

package com.x9710.es.test;

import org.apache.http.entity.ContentType;
import org.apache.http.nio.entity.NStringEntity;
import org.codehaus.jettison.json.JSONObject;
import org.elasticsearch.client.Response;
import org.elasticsearch.client.ResponseListener;
import org.elasticsearch.client.RestClient;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.HashMap;

/**
 * Elastic Search 低級 Api 測試類
 *
 * @author 楊高超
 * @since 2018-01-11
 */
public class LowLeveApiTest {
    RestClient restClient = null;

    @Before
    public void before() {
        restClient = new IndexInitUtil().initLowLevelClient();
    }

    @Test
    public void testLocateAuthorIndex() {
        try {
            Response response = restClient.performRequest("GET", "/author_test");
            String responseData = readResposne(response);
            Assert.assertTrue(new JSONObject(responseData).has("author_test"));
            System.out.println(responseData);
        } catch (Exception e) {
            e.printStackTrace();
            Assert.assertTrue(false);
        }
    }


    @Test
    public void testQueryAuthDoc() {
        try {
            String queryJson = "{\n" +
                    " \"query\": {\n" +
                    " \"match\": {\n" +
                    " \"des\": \"Java\"\n" +
                    " }\n" +
                    " }\n" +
                    "}";
            Response response = restClient.performRequest("POST",
                    "/author_test/_search",
                    new HashMap<String, String>(),
                    new NStringEntity(queryJson, ContentType.APPLICATION_JSON));

            String responseData = readResposne(response);
            JSONObject responseJson = new JSONObject(responseData);
            Assert.assertTrue(responseJson.has("hits")
                    && responseJson.getJSONObject("hits").getInt("total") == 1);
            System.out.println(responseData);
        } catch (Exception e) {
            e.printStackTrace();
            Assert.assertTrue(false);
        }
    }

    @Test
    public void testQueryAuthDocAsy() {
        try {
String queryJson = "{\n" +
        " \"query\": {\n" +
        " \"match\": {\n" +
        " \"des\": \"軟件\"\n" +
        " }\n" +
        " }\n" +
        "}";


restClient.performRequestAsync("POST",
        "/author_test/_search",
        new HashMap<String, String>(),
        new NStringEntity(queryJson, ContentType.APPLICATION_JSON), new ResponseListener() {
            public void onSuccess(Response response) {
                try {
                    String responseData = readResposne(response);
                    System.out.println("******* search success ******");
                    System.out.println(responseData);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }

            public void onFailure(Exception exception) {
                exception.printStackTrace();
            }
        });
        } catch (Exception e) {
            e.printStackTrace();
            Assert.assertTrue(false);
        }
    }


    @After
    public void after() {
        try {
            if (restClient != null) {
                restClient.performRequest("DELETE", "/author_test");
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (restClient != null) {
                try {
                    restClient.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }

    private String readResposne(Response response) throws Exception {
        BufferedReader brd = new BufferedReader(new BufferedReader(new InputStreamReader(response.getEntity().getContent())));
        String line;
        StringBuilder respongseContext = new StringBuilder();

        while ((line = brd.readLine()) != null) {
            respongseContext.append(line).append("\n");
        }
        //rd.close();
        if (respongseContext.length() > 0) {
            respongseContext.deleteCharAt(respongseContext.length() - 1);
        }
        return respongseContext.toString();
    }
}

複製代碼

高級 Api 測試樣例

package com.x9710.es.test;

import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;

/**
 * Elastic Search 高級 Api 測試類
 *
 * @author 楊高超
 * @since 2018-01-11
 */
public class HighLevelApiTest {
    RestHighLevelClient restClient = null;

    @Before
    public void before() {
        restClient = new IndexInitUtil().initHighLevelClient();
    }


    @Test
    public void testQueryAuthDoc() {
        try {
SearchRequest searchRequest = new SearchRequest("author_test");
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
sourceBuilder.query(QueryBuilders.matchQuery("des", "軟件"));
sourceBuilder.from(0);
sourceBuilder.size(5);
searchRequest.source(sourceBuilder);
SearchResponse response = restClient.search(searchRequest);
            Assert.assertTrue(response.getHits().getTotalHits() == 2);
            System.out.println(response.toString());
        } catch (Exception e) {
            e.printStackTrace();
            Assert.assertTrue(false);
        }
    }


    @After
    public void after() {
        try {
            if (restClient != null) {
                restClient.indices().deleteIndex(new DeleteIndexRequest("author_test"));
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (restClient != null) {
                try {
                    restClient.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
複製代碼

後記

前面也提到了,社區也貢獻了不少 Elastic Search 的客戶端庫,可是沒有時間去研究。若是有人用過以爲好用,但願推薦。

原文發表在簡書上。

相關文章
相關標籤/搜索