【Elasticsearch】導入到 Spring Boot 並實作 CRUD


https://unsplash.com/photos/_DyUcJalGAc

雖然 ElasticSearch 提供 REST API 讓我們能直接呼叫,但在實際進行後端程式開發時,通常會使用專門的函式庫(library)來存取服務。

接下來的三篇文章,會以 Spring Boot 後端專案為基礎,並使用官方建議的「Java API Client」這款 library,進行存取 ES 的實作。文末也會附上本文完成後的程式專案供讀者參考。

一、程式專案準備

(一)建立專案

首先我們要準備一個 Spring Boot 專案。筆者將透過 Spring Initializr 來產生,並於畫面右方一同加入「Spring Web」的依賴。

在 Spring Initializr 添加依賴

接著在 pom 檔加入 ElasticSearch 提供的「Java API Client」的 library。

<dependency>
    <groupId>co.elastic.clients</groupId>
    <artifactId>elasticsearch-java</artifactId>
    <version>8.3.1</version>
</dependency>

若啟動程式時發生「ClassNotFoundException: jakarta.json.JsonException」的錯誤,代表發生了 library 版本的衝突。請再加入以下 library,令程式使用此版本。這是一個有關 JSON 處理的 library。

<dependency>
    <groupId>jakarta.json</groupId>
    <artifactId>jakarta.json-api</artifactId>
    <version>2.1.0</version>
</dependency>

(二)準備 client

現在讓我們在 Spring Boot 專案中配置 ES 的連線設定。請新增一個 Configuration 類別,並在裡面配置一個 ElasticsearchClient 的元件。

上面的 ElasticsearchClient 是由 library 所提供。在此我們只要簡單地給予 ES 服務所在的 IP 與 port 號即可。而 JacksonJsonpMapper 物件則是用來將 JSON 與 Java 物件互相轉換(ORM),畢竟 ES 的 document 是以 JSON 格式來儲存。

在後續的範例,都會使用這個 client 元件來發出請求。

(三)資料介紹

本文所示範的是將學生資料存放到 ES,因此筆者設計了一個學生(Student)類別,其內部還包含課程(Course)與職務(Job)的資料。


二、建構 Repository 類別

為了存取 ES,我們可準備一個 Repository 層(DAO)來封裝邏輯。由於 Java API Client 的 library 不像 Spring Data 有提供便利的 ElasticsearchRepository,因此在本文,會刻出能夠對單一 document 做 CRUD 的 Repository。

以下建立一個名為 StudentEsRepository 的類別。由於要存取 ES 上的某個 index,所以從建構子傳入 client 元件與 index 名稱。

最後將其配置為元件。此處於配置時呼叫了一個 init 方法,該方法是準備用來做初始化。

事實上,撰寫程式對 ES 進行 CRUD 的核心邏輯,並不會因為 document 類別的不同而有顯著差異,所以這部份是可以做成一個抽象類別。但筆者為了避免將範例程式複雜化,因此只實作 concrete class。

從下一節開始,將向讀者示範如何透過 library 撰寫存取 ES 的程式。

三、建立與刪除 index

在上一節配置 StudentEsRepository 元件時,呼叫了一個 init 初始化方法,本節就來完成它。筆者想做的事情,是在 server 啟動時,刪除 index 後重新建立,並進行 mapping 的設定。

(一)建立 index

以下是建立 index 的範例程式。

基本上就是建立一個 CreateIndexRequest 物件,並指定 index 名稱。接著呼叫 client 的 indices 方法,根據要對 index 執行的操作,將 request 物件傳入即可。

(二)刪除 index

以下是刪除 index 的範例程式,同樣都是建立一個 request 物件,再呼叫 client。

讀者能注意到,建立 ES 的相關物件時,可用 builder 的方式建立。若添加的參數很少時,亦可選擇呼叫「of」方法,改成在裡面使用 builder,讓程式碼簡潔些。

(三)設定 mapping

當 document 的資料類別中,有新的欄位需要特別指定 mapping時(或是想修改),那就需要重新建立 index,在還沒有任何 document 時完成設定。

以下建立一個方法,它會回傳一個 Map,其 key 為 document 欄位名稱,而 value 為 mapping 資訊。

此處的例子是將 englishIssuedDate 欄位的 type 指定為「date」。否則 ES 會將 Java 的 Date 物件以 long 儲存,可能會影響衰減函數(decay function)的使用方式。

接著再回到建立 index 的方法,將 mapping 資訊添加到 CreateIndexRequest 中。

(四)例外處理

從前面我們可以注意到,透過 client 發出請求時,都會有 IOException 這個 checked exception 要處理。由於筆者不想往上層丟,也不想一直寫出 try-catch 影響程式碼簡潔,所以使用 functional programming 的做法來處理。

下面建立一個 functional interface,它具有一個 get 方法可以回傳任意型態的值,但會拋出 IOException。

接著撰寫一個名為 execute 的方法,讓想要執行,卻會拋出 IOException 的一段程式,能以 IOSupplier 參數的形式傳入。呼叫其 get 方法實際執行後,可達到統一在這個方法中處理例外的效果。

如此一來,前面建立與刪除 index 的方式,便能改寫成如下:

四、新增 document

以下是新增 document 的範例程式。

此處建立 CreateRequest 物件,過程中指定了 index 名稱,以及 document 的 id 與內容。

接著呼叫 client 的 create 方法即可,並得到 CreateResponse 物件。新增完畢後,可從 CreateResponse 中取得 id,賦予給 document 物件後再回傳。


五、取得 document

以下是透過 id 取得 document 的範例程式。

將 GetRequest 物件,以及要轉換成的 document 類別傳給 client 的 get 方法後,會得到 GetResponse 物件。若有找到資料,則藉由 GetResponse 的 source 方法,可得到 document 物件,否則為 null。


六、更新 document

以下是更新 document 的範例程式,此處的更新指的是「覆蓋」。

在建立 IndexRequest 物件時,會從傳入的 document 物件取得 id,以決定要更新哪一筆資料。若未提供 id,則會被 ES 視為新增資料。也因此方法中固定會做一次賦予 id 的動作,再回傳 document。

七、刪除 document

以下是透過 id 刪除 document 的範例程式。


八、批次新增 document

若同時有多個 document 要儲存到 ES,透過批次新增的做法,效率會好一些,但實作方式也稍微繁瑣。

主要是建立出 BulkRequest 物件,再傳入 client 的 bulk 方法。至於 BulkRequest 的產生方式,是先透過 document 物件產生 CreateOperation 物件,再轉為 BulkOperation 物件,將其附加到 BulkRequest 的 builder 上。

執行後,會得到 BulkResponse 物件,並可從中取得多個 BulkResponseItem 物件。我們可從 BulkResponseItem 中獲取 document id,賦予給當初傳入的 document 物件。

附帶一提,一開始將多個 document 物件附加到 BulkRequest 的順序,就是後來取得 BulkResponseItem 的順序,所以此處單純使用迴圈賦予 id 是沒有問題的。


九、訂定 REST API

最後再建立後端的 Controller,提供以下 REST API。

至此已經將 Controller 接上 Repository 了,讀者可從外部透過這些 API,對 ES 的 document 做讀寫。

本文的完成專案:
https://github.com/ntub46010/SpringBootUsingElasticsearch/tree/part1-implement-crud

上一篇:【ElasticSearch 8】使用 function score 在查詢時評分

下一篇:【ElasticSearch 8】使用 Java API Client 實作查詢條件與搜尋

留言