1. 程式人生 > 程式設計 >SpringBoot 如何整合 ES 實現 CRUD 操作

SpringBoot 如何整合 ES 實現 CRUD 操作

本文介紹 Spring Boot 專案中整合 ElasticSearch 並實現 CRUD 操作,包括分頁、滾動等功能。
之前在公司使用 ES,一直用的是前輩封裝好的包,最近希望能夠從原生的 Spring Boot/ES 語法角度來學習 ES 的相關技術。希望對大家有所幫助。

本文為 spring-boot-examples 系列文章節選,示例程式碼已上傳至 https://github.com/laolunsi/spring-boot-examples

安裝 ES 與視覺化工具

前往 ES 官方 https://www.elastic.co/cn/downloads/elasticsearch 進行,如 windows 版本只需要下載安裝包,啟動 elasticsearch.bat 檔案,瀏覽器訪問 http://localhost:9200

SpringBoot 如何整合 ES 實現 CRUD 操作

如此,表示 ES 安裝完畢。

為更好地檢視 ES 資料,再安裝一下 elasticsearch-head 視覺化外掛。前往下載地址:https://github.com/mobz/elasticsearch-head
主要步驟:

  • git clone git://github.com/mobz/elasticsearch-head.git
  • cd elasticsearch-head
  • npm install
  • npm run start
  • open http://localhost:9100/

可能會出現如下情況:

SpringBoot 如何整合 ES 實現 CRUD 操作

發現是跨域的問題。
解決辦法是在 elasticsearch 的 config 資料夾中的 elasticsearch.yml 中新增如下兩行配置:

http.cors.enabled: true
http.cors.allow-origin: "*"

重新整理頁面:

SpringBoot 如何整合 ES 實現 CRUD 操作

這裡的 article 索引就是我通過 spring boot 專案自動建立的索引。
下面我們進入正題。

Spring Boot 引入 ES

建立一個 spring-boot 專案,引入 es 的依賴:

 <dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
 </dependency>

配置 application.yml:

server:
 port: 8060

spring:
 elasticsearch:
 rest:
 uris: http://localhost:9200

建立一個測試的物件,article:

import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.Document;

import java.util.Date;

@Document(indexName = "article")
public class Article {

 @Id
 private String id;
 private String title;
 private String content;
 private Integer userId;
 private Date createTime;

 // ... igonre getters and setters
}

下面介紹 Spring Boot 中操作 ES 資料的三種方式:

  • 實現 ElasticsearchRepository 介面
  • 引入 ElasticsearchRestTemplate
  • 引入 ElasticsearchOperations

實現對應的 Repository:

import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;

public interface ArticleRepository extends ElasticsearchRepository<Article,String> {

}

下面可以使用這個 ArticleRepository 來操作 ES 中的 Article 資料。
我們這裡沒有手動建立這個 Article 對應的索引,由 elasticsearch 預設生成。

下面的介面,實現了 spring boot 中對 es 資料進行插入、更新、分頁查詢、滾動查詢、刪除等操作。可以作為一個參考。其中,使用了 Repository 來獲取、儲存、刪除 ES 資料,使用 ElasticsearchRestTemplate 或 ElasticsearchOperations 來進行分頁/滾動查詢。

根據 id 獲取/刪除資料

 @Autowired
 private ArticleRepository articleRepository;

 @GetMapping("{id}")
 public JsonResult findById(@PathVariable String id) {
 Optional<Article> article = articleRepository.findById(id);
 JsonResult jsonResult = new JsonResult(true);
 jsonResult.put("article",article.orElse(null));
 return jsonResult;
 }

 @DeleteMapping("{id}")
 public JsonResult delete(@PathVariable String id) {
 // 根據 id 刪除
 articleRepository.deleteById(id);
 return new JsonResult(true,"刪除成功");
 }

儲存資料

 @PostMapping("")
 public JsonResult save(Article article) {
 // 新增或更新
 String verifyRes = verifySaveForm(article);
 if (!StringUtils.isEmpty(verifyRes)) {
  return new JsonResult(false,verifyRes);
 }

 if (StringUtils.isEmpty(article.getId())) {
  article.setCreateTime(new Date());
 }

 Article a = articleRepository.save(article);
 boolean res = a.getId() != null;
 return new JsonResult(res,res ? "儲存成功" : "");
 }

 private String verifySaveForm(Article article) {
 if (article == null || StringUtils.isEmpty(article.getTitle())) {
  return "標題不能為空";
 } else if (StringUtils.isEmpty(article.getContent())) {
  return "內容不能為空";
 }

 return null;
 }

分頁查詢資料

 @Autowired
 private ElasticsearchRestTemplate elasticsearchRestTemplate;

 @Autowired
 ElasticsearchOperations elasticsearchOperations;

 @GetMapping("list")
 public JsonResult list(Integer currentPage,Integer limit) {
 if (currentPage == null || currentPage < 0 || limit == null || limit <= 0) {
  return new JsonResult(false,"請輸入合法的分頁引數");
 }
 // 分頁列表查詢
 // 舊版本的 Repository 中的 search 方法被廢棄了。
 // 這裡採用 ElasticSearchRestTemplate 或 ElasticsearchOperations 來進行分頁查詢

 JsonResult jsonResult = new JsonResult(true);
 NativeSearchQuery query = new NativeSearchQuery(new BoolQueryBuilder());
 query.setPageable(PageRequest.of(currentPage,limit));

 // 方法1:
 SearchHits<Article> searchHits = elasticsearchRestTemplate.search(query,Article.class);

 // 方法2:
 // SearchHits<Article> searchHits = elasticsearchOperations.search(query,Article.class);

 List<Article> articles = searchHits.getSearchHits().stream().map(SearchHit::getContent).collect(Collectors.toList());
 jsonResult.put("count",searchHits.getTotalHits());
 jsonResult.put("articles",articles);
 return jsonResult;
 }

滾動查詢資料

 @GetMapping("scroll")
 public JsonResult scroll(String scrollId,Integer size) {
 // 滾動查詢 scroll api
 if (size == null || size <= 0) {
  return new JsonResult(false,"請輸入每頁查詢數");
 }
 NativeSearchQuery query = new NativeSearchQuery(new BoolQueryBuilder());
 query.setPageable(PageRequest.of(0,size));
 SearchHits<Article> searchHits = null;
 if (StringUtils.isEmpty(scrollId)) {
  // 開啟一個滾動查詢,設定該 scroll 上下文存在 60s
  // 同一個 scroll 上下文,只需要設定一次 query(查詢條件)
  searchHits = elasticsearchRestTemplate.searchScrollStart(60000,query,Article.class,IndexCoordinates.of("article"));
  if (searchHits instanceof SearchHitsImpl) {
  scrollId = ((SearchHitsImpl) searchHits).getScrollId();
  }
 } else {
  // 繼續滾動
  searchHits = elasticsearchRestTemplate.searchScrollContinue(scrollId,60000,IndexCoordinates.of("article"));
 }

 List<Article> articles = searchHits.getSearchHits().stream().map(SearchHit::getContent).collect(Collectors.toList());
 if (articles.size() == 0) {
  // 結束滾動
  elasticsearchRestTemplate.searchScrollClear(Collections.singletonList(scrollId));
  scrollId = null;
 }

 if (scrollId == null) {
  return new JsonResult(false,"已到末尾");
 } else {
  JsonResult jsonResult = new JsonResult(true);
  jsonResult.put("count",searchHits.getTotalHits());
  jsonResult.put("size",articles.size());
  jsonResult.put("articles",articles);
  jsonResult.put("scrollId",scrollId);
  return jsonResult;
 }

 }

ES 深度分頁 vs 滾動查詢

上次遇到一個問題,同事跟我說日誌檢索的介面太慢了,問我能不能優化一下。開始使用的是深度分頁,即 1,2,3..10,這樣的分頁查詢,查詢條件較多(十多個引數)、查詢資料量較大(單個日誌索引約 2 億條資料)。

分頁查詢速度慢的原因在於:ES 的分頁查詢,如查詢第 100 頁資料,每頁 10 條,是先從每個分割槽 (shard,一個索引預設是 5 個 shard) 中把命中的前 100 * 10 條資料查出來,然後由協調節點進行合併等操作,最後給出第 100 頁的資料。也就是說,實際被載入到記憶體中的資料遠超過理想情況。

這樣,索引的 shard 越大,查詢頁數越多,查詢速度就越慢。
ES 預設的 max_result_window 是 10000 條,也就是正常情況下,用分頁查詢到 10000 條資料時,就不會再返回下一頁資料了。

如果不需要進行跳頁,比如直接查詢第 100 頁資料,或者資料量非常大,那麼可以考慮用 scroll 查詢。
在 scroll 查詢下,第一次需要根據查詢引數開啟一個 scroll 上下文,設定上下文快取時間。以後的滾動只需要根據第一次返回的 scrollId 來進行即可。

scroll 只支援往下滾動,如果想要往回滾動,還可以根據 scrollId 快取查詢結果,這樣就可以實現上下滾動查詢了 —— 就像大家經常使用的淘寶商品檢索時上下滾動一樣。

以上就是SpringBoot 如何整合 ES 實現 CRUD 操作的詳細內容,更多關於SpringBoot實現 CRUD 操作的資料請關注我們其它相關文章!