Spring Boot 入門示例(十七):Spring Boot 集成 ElasticSearch (7.x 版本)搜索引擎

Spring Boot 集成 ElasticSearch (7.x 版本)搜索引擎

ElasticSearch 是一個基於 Lucene 的搜索服務器。它提供了一個分佈式多用戶能力的全文搜索引擎,基於 RESTful web 接口。Elasticsearch 是用 Java 語言開發的,並作爲 Apache 許可條款下的開放源碼發佈,是一種流行的企業級搜索引擎。ElasticSearch 用於雲計算中,能夠達到實時搜索、穩定、可靠、快速、安裝使用方便。官方客戶端在 Java、.NET、PHP、Python、Apache Groovy、Ruby 和許多其他語言中都是可用的。

本文主要講述了 Spring Boot 如何集成 ElasticSearch 搜索引擎,並使用 ElasticSearch 官方提供的 Java High Level REST Client 進行 Java 客戶端操作。

環境準備

  • ElasticSearch 7.5.1
  • Spring Boot 2.1.9

首先需要安裝 ElasticSearch 環境,並且最好安裝一個 ElasticSearch 可視化界面(這裏推薦 ElasticSearch Head 插件)便於我們查看數據。

ElasticSearch 和 ElasticSearch Head 安裝可以參考本人寫的這篇博客:Widows 環境下安裝 ElasticSearch 並配置 ElasticSearch Head 插件

添加依賴

在 pom.xml 文件中,添加 ElasticSearch 提供的 Java High Level REST Client 相關依賴。

注意:在添加 ElasticSearch 相關依賴時,一定要指明版本號。如果不指定版本號,會直接繼承 Spring Boot 的版本號,這樣會導致與 ElasticSearch 的版本不一致,而出現錯誤。

<!-- ElasticSearch High Level Client -->
<dependency>
    <groupId>org.elasticsearch.client</groupId>
    <artifactId>elasticsearch-rest-high-level-client</artifactId>
    <version>7.5.1</version>
</dependency>

<dependency>
    <groupId>org.elasticsearch</groupId>
    <artifactId>elasticsearch</artifactId>
    <version>7.5.1</version>
</dependency>

<dependency>
    <groupId>org.elasticsearch.client</groupId>
    <artifactId>elasticsearch-rest-client</artifactId>
    <version>7.5.1</version>
</dependency>
<!-- ./ ElasticSearch High Level Client -->

配置 RestHighLevelClient

這裏進行 RestHighLevelClient 配置,用於操作 ElasticSearch。

/**
 * EsRestHighLevelClient
 *
 * @author star
 */
@Configuration
public class EsRestHighLevelClientConfig {

    @Value("${spring.elasticsearch.rest.scheme}")
    private String scheme;

    @Value("${spring.elasticsearch.rest.ip-address}")
    private List<String> ipAddressList;

    @Bean
    public RestHighLevelClient restHighLevelClient() {
        return new RestHighLevelClient(RestClient.builder(this.createHttpHost()));
    }

    /**
     * 創建 HttpHost 對象
     *
     * @return 返回 HttpHost 對象數組
     */
    private HttpHost[] createHttpHost() {
        Asserts.check(!CollectionUtils.isEmpty(ipAddressList), "ElasticSearch cluster ip address cannot empty");

        HttpHost[] httpHosts = new HttpHost[ipAddressList.size()];
        for (int i = 0, len = ipAddressList.size(); i < len; i++) {
            String ipAddress = ipAddressList.get(i);
            String[] values = ipAddress.split(":");

            String ip = values[0];
            int port = Integer.parseInt(values[1]);
            // 創建 HttpHost
            httpHosts[i] = new HttpHost(ip, port, scheme);
        }

        return httpHosts;

    }
}

編寫 ElaticSearch 相關 API

  • 索引創建

ElaticSearch 7.x 的版本中已經廢棄了 mapping,棄用了 type,也就是一個 index 相當於一個表。

詳情見官方文檔關於映射類型去除部分。以下是關於 ElaticSearch 7.x 的映射類型描述:
映射類型

/**
  * 創建 ES 索引
  *
  * @param index      索引
  * @param properties 文檔屬性集合
  * @return 返回 true,表示創建成功
  * @throws IOException
  */
public boolean createIndex(String index, Map<String, Map<String, Object>> properties) throws IOException {
    XContentBuilder builder = XContentFactory.jsonBuilder();
    // ES 7.0 後的版本中,已經棄用 type
    builder.startObject()
            .startObject("mappings")
            .field("properties", properties)
            .endObject()
            .startObject("settings")
            .field("number_of_shards", DEFAUT_SHARDS)
            .field("number_of_replicas", DEFAUT_REPLICAS)
            .endObject()
            .endObject();

    CreateIndexRequest request = new CreateIndexRequest(index).source(builder);
    CreateIndexResponse response = restHighLevelClient.indices().create(request, RequestOptions.DEFAULT);

    return response.isAcknowledged();
}
  • 判斷索引是否存在
/**
 * 判斷索引是否存在
 *
 * @param index 索引
 * @return 返回 true,表示存在
 */ 
public boolean isExistIndex(String index) throws IOException {
    GetIndexRequest getIndexRequest = new GetIndexRequest(index);
    getIndexRequest.local(false);
    getIndexRequest.humanReadable(true);
    getIndexRequest.includeDefaults(false);

    return restHighLevelClient.indices().exists(getIndexRequest, RequestOptions.DEFAULT);
}
  • 刪除索引
/**
 * 刪除索引
 *
 * @param index 索引
 * @return 返回 true,表示刪除成功
 */
public boolean deleteIndex(String index) throws IOException {
    try {
        DeleteIndexRequest request = new DeleteIndexRequest(index);
        AcknowledgedResponse response = restHighLevelClient.indices().delete(request, RequestOptions.DEFAULT);

        return response.isAcknowledged();
    } catch (ElasticsearchException exception) {
        if (exception.status() == RestStatus.NOT_FOUND) {
            throw new NotFoundException("Not found index: " + index);
        }

        throw exception;
    }
}
  • 保存文檔
/**
 * 保存文檔
 * <p>
 * 如果文檔存在,則更新文檔;如果文檔不存在,則保存文檔。
 *
 * @param document 文檔數據
 */
public void save(String index, ElasticSearchDocument<?> document) throws IOException {
    IndexRequest indexRequest = new IndexRequest(index);
    indexRequest.id(document.getId());
    indexRequest.source(JSON.toJSONString(document.getData()), XContentType.JSON);
    // 保存文檔數據
    restHighLevelClient.index(indexRequest, RequestOptions.DEFAULT);

}

/**
 * 批量保存文檔
 * <p>
 * 如果集合中有些文檔已經存在,則更新文檔;不存在,則保存文檔。
 *
 * @param index        索引
 * @param documentList 文檔集合
 */
public void saveAll(String index, List<ElasticSearchDocument<?>> documentList) throws IOException {
    if (CollectionUtils.isEmpty(documentList)) {
        return;
    }
    // 批量請求
    BulkRequest bulkRequest = new BulkRequest();
    documentList.forEach(doc -> {
        bulkRequest.add(new IndexRequest(index)
                .id(doc.getId())
                .source(JSON.toJSONString(doc.getData()), XContentType.JSON));
    });

    restHighLevelClient.bulk(bulkRequest, RequestOptions.DEFAULT);

}
  • 刪除文檔
/**
 * 根據文檔 ID 刪除文檔
 *
 * @param index 索引
 * @param id    文檔 ID
 */
public void delete(String index, String id) throws IOException {
    DeleteRequest deleteRequest = new DeleteRequest(index, id);

    restHighLevelClient.delete(deleteRequest, RequestOptions.DEFAULT);
}

/**
 * 根據查詢條件刪除文檔
 *
 * @param index        索引
 * @param queryBuilder 查詢條件構建器
 */
public void deleteByQuery(String index, QueryBuilder queryBuilder) throws IOException {
    DeleteByQueryRequest deleteRequest = new DeleteByQueryRequest(index).setQuery(queryBuilder);
    deleteRequest.setConflicts("proceed");

    restHighLevelClient.deleteByQuery(deleteRequest, RequestOptions.DEFAULT);

}

/**
 * 根據文檔 ID 批量刪除文檔
 *
 * @param index  索引
 * @param idList 文檔 ID 集合
 */
public void deleteAll(String index, List<String> idList) throws IOException {
    if (CollectionUtils.isEmpty(idList)) {
        return;
    }
    BulkRequest bulkRequest = new BulkRequest();
    idList.forEach(id -> bulkRequest.add(new DeleteRequest(index, id)));

    restHighLevelClient.bulk(bulkRequest, RequestOptions.DEFAULT);
}
  • 獲取文檔
/**
 * 根據索引和文檔 ID 獲取數據
 *
 * @param index 索引
 * @param id    文檔 ID
 * @param <T>   數據類型
 * @return T    返回 T 類型的數據
 */
public <T> T get(String index, String id, Class<T> resultType) throws IOException {
    GetRequest getRequest = new GetRequest(index, id);
    GetResponse response = restHighLevelClient.get(getRequest, RequestOptions.DEFAULT);
    String resultAsString = response.getSourceAsString();

    return JSON.parseObject(resultAsString, resultType);
}

/**
 * 條件查詢
 *
 * @param index         索引
 * @param sourceBuilder 條件查詢構建起
 * @param <T>           數據類型
 * @return T 類型的集合
 */
public <T> List<T> searchByQuery(String index, SearchSourceBuilder sourceBuilder, Class<T> resultType) throws IOException {
    // 構建查詢請求
    SearchRequest searchRequest = new SearchRequest(index).source(sourceBuilder);
    // 獲取返回值
    SearchResponse response = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
    SearchHit[] hits = response.getHits().getHits();
    // 創建空的查詢結果集合
    List<T> results = new ArrayList<>(hits.length);
    for (SearchHit hit : hits) {
        // 以字符串的形式獲取數據源
        String sourceAsString = hit.getSourceAsString();
        results.add(JSON.parseObject(sourceAsString, resultType));
    }

    return results;

}

演示

  • 使用 Postman 調用接口 POST http://localhost:8080/api/users/batch 向 ElasticSearch 中插入 2 條測試數據:
    es-save

  • 使用可視化插件 ElasticSearch Head 查看數據:
    es-head-show

  • 根據 ID 獲取數據:
    es-get-id

  • 根據 name 獲取數據:
    es-get-name

參考

ElasticSearch Java Rest Client:https://www.elastic.co/guide/en/elasticsearch/client/java-rest/7.5/java-rest-high.html

ElasticSearch Reference:https://www.elastic.co/guide/en/elasticsearch/reference/current/index.html

Spring Boot 實戰之 NoSQL 整合(ElasticSearch 7.3 版本):https://cloud.tencent.com/developer/article/1522132

後記

由於自身能力有限,若有錯誤或者不當之處,還請大家批評指正,一起學習交流!

GitHub 源碼地址:springboot-elasticsearch

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章