SpringBoot 如何整合 ES 實現(xiàn) CRUD 操作
本文介紹 Spring Boot 項目中整合 ElasticSearch 并實現(xiàn) CRUD 操作,包括分頁、滾動等功能。
之前在公司使用 ES,一直用的是前輩封裝好的包,最近希望能夠從原生的 Spring Boot/ES 語法角度來學(xué)習(xí) ES 的相關(guān)技術(shù)。希望對大家有所幫助。
本文為 spring-boot-examples 系列文章節(jié)選,示例代碼已上傳至 https://github.com/laolunsi/spring-boot-examples
安裝 ES 與可視化工具
前往 ES 官方 https://www.elastic.co/cn/downloads/elasticsearch 進(jìn)行,如 windows 版本只需要下載安裝包,啟動 elasticsearch.bat 文件,瀏覽器訪問 http://localhost:9200
如此,表示 ES 安裝完畢。
為更好地查看 ES 數(shù)據(jù),再安裝一下 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/
可能會出現(xiàn)如下情況:
發(fā)現(xiàn)是跨域的問題。
解決辦法是在 elasticsearch 的 config 文件夾中的 elasticsearch.yml 中添加如下兩行配置:
http.cors.enabled: true http.cors.allow-origin: "*"
刷新頁面:
這里的 article 索引就是我通過 spring boot 項目自動創(chuàng)建的索引。
下面我們進(jìn)入正題。
Spring Boot 引入 ES
創(chuàng)建一個 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
創(chuàng)建一個測試的對象,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 數(shù)據(jù)的三種方式:
- 實現(xiàn) ElasticsearchRepository 接口
- 引入 ElasticsearchRestTemplate
- 引入 ElasticsearchOperations
實現(xiàn)對應(yīng)的 Repository:
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository; public interface ArticleRepository extends ElasticsearchRepository<Article, String> { }
下面可以使用這個 ArticleRepository 來操作 ES 中的 Article 數(shù)據(jù)。
我們這里沒有手動創(chuàng)建這個 Article 對應(yīng)的索引,由 elasticsearch 默認(rèn)生成。
下面的接口,實現(xiàn)了 spring boot 中對 es 數(shù)據(jù)進(jìn)行插入、更新、分頁查詢、滾動查詢、刪除等操作??梢宰鳛橐粋€參考。其中,使用了 Repository 來獲取、保存、刪除 ES 數(shù)據(jù),使用 ElasticsearchRestTemplate 或 ElasticsearchOperations 來進(jìn)行分頁/滾動查詢。
根據(jù) id 獲取/刪除數(shù)據(jù)
@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) { // 根據(jù) id 刪除 articleRepository.deleteById(id); return new JsonResult(true, "刪除成功"); }
保存數(shù)據(jù)
@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 "標(biāo)題不能為空"; } else if (StringUtils.isEmpty(article.getContent())) { return "內(nèi)容不能為空"; } return null; }
分頁查詢數(shù)據(jù)
@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, "請輸入合法的分頁參數(shù)"); } // 分頁列表查詢 // 舊版本的 Repository 中的 search 方法被廢棄了。 // 這里采用 ElasticSearchRestTemplate 或 ElasticsearchOperations 來進(jìn)行分頁查詢 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; }
滾動查詢數(shù)據(jù)
@GetMapping("scroll") public JsonResult scroll(String scrollId, Integer size) { // 滾動查詢 scroll api if (size == null || size <= 0) { return new JsonResult(false, "請輸入每頁查詢數(shù)"); } NativeSearchQuery query = new NativeSearchQuery(new BoolQueryBuilder()); query.setPageable(PageRequest.of(0, size)); SearchHits<Article> searchHits = null; if (StringUtils.isEmpty(scrollId)) { // 開啟一個滾動查詢,設(shè)置該 scroll 上下文存在 60s // 同一個 scroll 上下文,只需要設(shè)置一次 query(查詢條件) searchHits = elasticsearchRestTemplate.searchScrollStart(60000, query, Article.class, IndexCoordinates.of("article")); if (searchHits instanceof SearchHitsImpl) { scrollId = ((SearchHitsImpl) searchHits).getScrollId(); } } else { // 繼續(xù)滾動 searchHits = elasticsearchRestTemplate.searchScrollContinue(scrollId, 60000, Article.class, IndexCoordinates.of("article")); } List<Article> articles = searchHits.getSearchHits().stream().map(SearchHit::getContent).collect(Collectors.toList()); if (articles.size() == 0) { // 結(jié)束滾動 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 滾動查詢
上次遇到一個問題,同事跟我說日志檢索的接口太慢了,問我能不能優(yōu)化一下。開始使用的是深度分頁,即 1,2,3..10, 這樣的分頁查詢,查詢條件較多(十多個參數(shù))、查詢數(shù)據(jù)量較大(單個日志索引約 2 億條數(shù)據(jù))。
分頁查詢速度慢的原因在于:ES 的分頁查詢,如查詢第 100 頁數(shù)據(jù),每頁 10 條,是先從每個分區(qū) (shard,一個索引默認(rèn)是 5 個 shard) 中把命中的前 100 * 10 條數(shù)據(jù)查出來,然后由協(xié)調(diào)節(jié)點(diǎn)進(jìn)行合并等操作,最后給出第 100 頁的數(shù)據(jù)。也就是說,實際被加載到內(nèi)存中的數(shù)據(jù)遠(yuǎn)超過理想情況。
這樣,索引的 shard 越大,查詢頁數(shù)越多,查詢速度就越慢。
ES 默認(rèn)的 max_result_window 是 10000 條,也就是正常情況下,用分頁查詢到 10000 條數(shù)據(jù)時,就不會再返回下一頁數(shù)據(jù)了。
如果不需要進(jìn)行跳頁,比如直接查詢第 100 頁數(shù)據(jù),或者數(shù)據(jù)量非常大,那么可以考慮用 scroll 查詢。
在 scroll 查詢下,第一次需要根據(jù)查詢參數(shù)開啟一個 scroll 上下文,設(shè)置上下文緩存時間。以后的滾動只需要根據(jù)第一次返回的 scrollId 來進(jìn)行即可。
scroll 只支持往下滾動,如果想要往回滾動,還可以根據(jù) scrollId 緩存查詢結(jié)果,這樣就可以實現(xiàn)上下滾動查詢了 —— 就像大家經(jīng)常使用的淘寶商品檢索時上下滾動一樣。
以上就是SpringBoot 如何整合 ES 實現(xiàn) CRUD 操作的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot實現(xiàn) CRUD 操作的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Spring?Boot使用MyBatis進(jìn)行兩個表的關(guān)聯(lián)
本文主要介紹了Spring?Boot使用MyBatis進(jìn)行兩個表的關(guān)聯(lián),通過實例演示了如何使用MyBatis的XML映射文件和注解實現(xiàn)關(guān)聯(lián)操作,具有一定的參考價值,感興趣的可以了解一下2023-09-09Java 中使用數(shù)組存儲和操作數(shù)據(jù)
本文將介紹Java中常用的數(shù)組操作方法,通過詳細(xì)的示例和解釋,幫助讀者全面理解和掌握這些方法,具有一定的參考價值,感興趣的可以了解一下2023-09-09Java虛擬機(jī)JVM類加載機(jī)制(從類文件到虛擬機(jī))
所謂的類加載機(jī)制就是虛擬機(jī)將class文件加載到內(nèi)存,并對數(shù)據(jù)進(jìn)行驗證,轉(zhuǎn)換解析和初始化,形成虛擬機(jī)可以直接使用的java類型,本文給大家介紹類加載機(jī)制過程從類文件到虛擬機(jī)的詳細(xì)說明,感興趣的朋友跟隨小編一起看看吧2021-06-06SpringBoot加載多個配置文件實現(xiàn)dev、product多環(huán)境切換的方法
這篇文章主要介紹了SpringBoot加載多個配置文件實現(xiàn)dev、product多環(huán)境切換,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-03-03