javascript
【主流技术】详解 Spring Boot 2.7.x 集成 ElasticSearch7.x 全过程(二)
- 前言
- 一、添加依賴
- 二、 yml 配置
- 三、注入依賴
-
四、CRUD 常用 API
- ES 實體類
- documents 操作
- 常見條件查詢(重點)
- 分頁查詢
- 排序
- 構造查詢
- 測試調用
- 五、文章小結
前言
ElasticSearch 簡稱 es,是一個開源的高擴展的分布式全文檢索引擎,目前最新版本已經到了8.11.x了。
它可以近乎實時的存儲、檢索數據,且其擴展性很好,是企業級應用中較為常見的檢索技術。
下面主要記錄學習 ElasticSearch7.x 的一些基本結構、在Spring Boot 項目里基本應用的過程,在這里與大家作分享交流。
一、添加依賴
這里引用的依賴是 starter-data-elasticsearch,版本應與 Spring Boot(我是2.7.2)的版本一致,并不是 Elasticsearch 的版本。
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-data-elasticsearch -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
<version>2.7.2</version>
</dependency>
二、 yml 配置
spring:
elasticsearch:
uris: http://遠程主機的公網IP:9200
username: 自己的用戶名
password: 自己的密碼
使用 Docker 安裝的 Elasticsearch 設置賬號/密碼教程:https://blog.csdn.net/qq_38669698/article/details/130529829
因為 ES 設置了密碼,所以 Kibana 的配置也需要修改:https://blog.csdn.net/weixin_45956631/article/details/130636880
三、注入依賴
-
(推薦)ElasticsearchRestTemplate 類來源于 org.springframework.data.elasticsearch.core 包,封裝了 Elasticsearch 的 RESTful API,使用起來很便捷。
//直接引入即可,無需額外的 Bean 配置和序列化配置 @Resource private ElasticsearchRestTemplate elasticTemplate; -
(推薦)ElasticsearchRepository 接口來源于 org.springframework.data.elasticsearch.repository 包, 該接口用于簡化對 Elasticsearch 中數據的操作。
public interface ArticleRepository extends ElasticsearchRepository<ESArticle, String>{}注:ESArticle 為實體類,String 表示唯一 Id 的數據類型。
-
(不推薦)在 Elasticsearch 7.15版本之后,官方已將它的高級客戶端 RestHighLevelClient 標記為棄用狀態,之后的版本會推薦新的 RestClient。
經過筆者對比實踐,無論是新/舊客戶端,在 Spring Boot 項目中都沒有上面前兩個使用起來便捷。但值得注意的是,很多企業以前的項目都會使用舊的 RestHighLevelClient 來寫業務。
@Resource private RestHighLevelClient highLevelClient; @Resource private RestClient restClient;
四、CRUD 常用 API
-
ES 實體類
和 MySQL、MongoDB 在 Spring 中的實體類一樣,需要將字段和類屬性進行映射,同樣還可以使用注解進行簡單配置。
以下是文章 ESArticle 的實體類,屬性包含標題、內容、標簽、點贊數/收藏數等:
@Data @Document(indexName = "article") @EqualsAndHashCode(callSuper = true) public class ESArticle extends BaseEntity implements Serializable { private static final String DATE_TIME_PATTERN = "yyyy-MM-dd HH:mm:ss"; /** * 唯一標識 id */ @Id @Field(type = FieldType.Text) private String id; /** * 標題,字段類型為 Text,沒有 String 類型;分詞類型為 ik 分詞器的最細顆粒度劃分法。 */ @Field(type = FieldType.Text, analyzer = "ik_max_word") private String title; /** * 內容 */ @Field(type = FieldType.Text, analyzer = "ik_max_word") private String content; /** * 標簽列表 */ private List<String> tags; /** * 點贊數 */ private Integer thumbNum; /** * 收藏數 */ private Integer favourNum; /** * 創建用戶 id */ @Field(type = FieldType.Text) private String userId; /** * 創建時間,單獨存儲,字段類型為 Date ,自定義格式 */ @Field(store = true, type = FieldType.Date, format = {}, pattern = DATE_TIME_PATTERN) private Date createTime; /** * 更新時間,單獨存儲,字段類型為 Date ,自定義格式 */ @Field(store = true, type = FieldType.Date, format = {}, pattern = DATE_TIME_PATTERN) private Date updateTime; /** * 是否刪除 */ private Integer isDelete; } -
documents 操作
documents 的概念和 MySQL 中的行類似,指的是一條條的記錄,但是 ES 里所有的數據都是 JSON 格式的,所以看起來就像是一個個文檔了。
以下簡單的 CRUD 都由 ArticleRepository 來完成,下一小節復雜的查詢交給 ElasticsearchRestTemplate 來完成。
-
新增(批量)
@Resource private ArticleMapper articleMapper; @Resource private ArticleRepository articleRepository; //todo: ES里的數據來源于數據庫,需要做遷移,業務數據不會直接寫進數據庫 //todo: 有全量和增量兩種方式做數據遷移,或者引入第三方框架處理 //todo: 此處暫不做數據遷移展示,就直接往 ES 里寫,然后就當 ES 里已經有數據了,再做 CRUD 以及查詢 @Override public Boolean addDocuments(){ LambdaQueryWrapper<Article> wrapper = new LambdaQueryWrapper<>(); List<Article> articleList = articleMapper.selectList(wrapper); if (CollectionUtils.isNotEmpty(articleList)){ // 這里是兩個實體的屬性轉換,這里不過多展開講 List<ESArticle> esArticleList = articleList.stream().map(ESArticle::dbToEs).collect(Collectors.toList()); articleRepository.saveAll(esArticleList); return Boolean.TRUE; } return Boolean.FALSE; } -
修改(更新)
//todo: 還可以使用 elasticTemplate 的 update() 來進行更新,不過一般沒有單獨針對 es 的數據更新需求 @Override public Boolean updateDocuments(){ ESArticle esArticle = articleRepository.findById("18094375634670546").orElse(null); if (Objects.nonNull(esArticle)){ esArticle.setTitle("測試修改標題更新操作"); articleRepository.save(esArticle); return Boolean.TRUE; } return Boolean.FALSE; } -
獲取
@Override public List<ESArticle> getESDocuments(){ List<ESArticle> list = Lists.newArrayList(); Iterable<ESArticle> esArticleList = this.articleRepository.findAll(Sort.by(Sort.Order.desc("id"))); esArticleList.forEach(list::add); return list; } -
刪除
@Override public Boolean deleteESDocuments(){ //如果存在該條 document 則繼續刪除 if (this.articleRepository.existsById("18094375634670546")){ this.articleRepository.deleteById("18094375634670546"); return Boolean.TRUE; } return Boolean.FALSE; }
-
-
常見條件查詢(重點)
以下會詳細地演示一下 BoolQueryBuilder 條件構造、常見 QueryBuilders 的方法等多條件復雜查詢場景:
//todo: 企業項目中真正的復雜條件查詢 @Override public PageInfo<ESArticle> testSearchFromES(ArticleSearchDTO articleSearchDTO){ //完整的合法 id String id = articleSearchDTO.getId(); //非法 id String notId = articleSearchDTO.getNotId(); //搜索框輸入的內容(實際會從標簽/內容/標題中查找) String searchText = articleSearchDTO.getSearchWord(); //單獨在標題中查找 String title = articleSearchDTO.getTitle(); //單獨在內容中查找 String content = articleSearchDTO.getContent(); //單獨在標簽中查找(全部標簽) List<String> tagList = articleSearchDTO.getTags(); //任意標簽 List<String> orTagList = articleSearchDTO.getOrTags(); //按照創建者的 userId 查找 String userId = articleSearchDTO.getUserId(); // 布爾查詢初始化 BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery(); // 過濾,首先被刪除的就不要了 boolQueryBuilder.filter(QueryBuilders.termQuery(this.fn.fnToFieldName(ESArticle::getIsDelete), NumberUtils.INTEGER_ZERO)); //如果輸入的是 id 那么就不對 id 分詞,然后過濾掉不符合該 id 的其它文檔 if (StringUtils.isNotBlank(id)) { boolQueryBuilder.filter(QueryBuilders.termQuery("id", id)); } //如果輸入的是非法 id 那么什么也查不到,取反(也就是所有)返回 if (StringUtils.isNotBlank(notId)) { boolQueryBuilder.mustNot(QueryBuilders.termQuery("id", notId)); } //創建者 userId 也不分詞,過濾掉不匹配的 if (StringUtils.isNotBlank(userId)) { boolQueryBuilder.filter(QueryBuilders.termQuery("createId", userId)); } // 必須包含所有標簽 if (CollectionUtils.isNotEmpty(tagList)) { for (String tag : tagList) { boolQueryBuilder.filter(QueryBuilders.termQuery("tags", tag)); } } // 包含任何一個標簽即可 if (CollectionUtils.isNotEmpty(orTagList)) { BoolQueryBuilder orTagBoolQueryBuilder = QueryBuilders.boolQuery(); // DB 實體中 tag 字段為 String,而 ES 實體該字段的類型為 List,所以做循環遍歷 for (String tag : orTagList) { orTagBoolQueryBuilder.should(QueryBuilders.termQuery("tags", tag)).minimumShouldMatch(1); } //filter 可以結合 bool 做更復雜的過濾 boolQueryBuilder.filter(orTagBoolQueryBuilder); } // 按關鍵詞檢索(主要的搜索框,關鍵詞會在兩個字段里匹配) if (StringUtils.isNotBlank(searchText)) { boolQueryBuilder.should(QueryBuilders.matchQuery("title", searchText)); boolQueryBuilder.should(QueryBuilders.matchQuery("content", searchText)); boolQueryBuilder.minimumShouldMatch(1); } // 單獨按標題檢索 if (StringUtils.isNotBlank(title)) { boolQueryBuilder.should(QueryBuilders.matchQuery("title", title)); } // 單獨按內容檢索 if (StringUtils.isNotBlank(content)) { boolQueryBuilder.should(QueryBuilders.matchQuery("content", content)); } } -
分頁查詢
Spring Data 自帶的分頁方案,即 PageRequest 對象:
// 分頁參數:起始頁為 0 long current = articleSearchDTO.getCurrent() - 1; long pageSize = articleSearchDTO.getPageSize(); PageRequest pageRequest = PageRequest.of((int) current, (int) pageSize); -
排序
設置了按條件排序則以排序字段為準來返回,沒設置排序則默認按照分數,即匹配度返回:
// 排序字段,可以支持多個 String sortField = articleSearchDTO.getSortField(); SortBuilder<?> sortBuilder = SortBuilders.scoreSort(); if (StringUtils.isNotBlank(sortField)) { sortBuilder = SortBuilders.fieldSort(sortField).order(SortOrder.DESC); } -
構造查詢
將所有的條件放進 NativeSearchQueryBuilder 對象,并調用elasticTemplate.search()方法,最后放入PageInfo(這里引入的是com.github.pagehelper)對象返回:
// 構造查詢 NativeSearchQuery searchQuery = new NativeSearchQueryBuilder() .withQuery(boolQueryBuilder) .withSorts(sortBuilder) .withPageable(pageRequest).build(); // 獲取查詢對象的結果:放入所有條件,指定索引實體 SearchHits<ESArticle> searchHits = elasticTemplate.search(searchQuery, ESArticle.class); //todo: 先以 ES 的數據為準,后期數據遷移再考慮使用 MySQL 的數據源 //初始化 page 對象 PageInfo<ESArticle> pageInfo = new PageInfo<>(); pageInfo.setList(searchHits.getSearchHits().stream().map(SearchHit::getContent).collect(Collectors.toList())); pageInfo.setTotal(searchHits.getTotalHits()); System.out.println(pageInfo); return pageInfo; -
測試調用
@Test public void testSearchFromES(){ ArticleSearchDTO articleSearchDTO = new ArticleSearchDTO(); articleSearchDTO.setId("18094375634670546"); //articleSearchDTO.setSearchWord("是"); //articleSearchDTO.setTitle("標題"); //articleSearchDTO.setTags(Collections.singletonList("es")); //articleSearchDTO.setSortField("createTime"); esTestService.testSearchFromES(articleSearchDTO); }
測試數據如下圖所示:
五、文章小結
使用 ElasticSearch 實現全文檢索的過程并不復雜,只要在業務需要的地方創建 ElasticSearch 索引,將數據放入索引中,就可以使用 ElasticSearch 集成在 Spring Boot 中對搜索對象進行查詢操作了。
無論是創建索引、精準匹配、還是字段高亮等操作,其本質上還是一個面向對象的過程。和 Java 中的其它“對象”一樣,只要靈活運用這些“對象”的使用規則和特性,就可以滿足業務上的需求。
關于 ElasticSearch7.x 的基本結構和在 Spring Boot 項目中的集成應用就和大家分享到這里。如有錯誤和不足,還期待大家的指正與交流。
參考文檔:
- ElasticSearch 官方查詢 API 文檔:https://www.elastic.co/guide/en/elasticsearch/reference/current/search.html
- Spring Data ElasticSearch 官方:https://docs.spring.io/spring-data/redis/docs/2.6.10/api/
總結
以上是生活随笔為你收集整理的【主流技术】详解 Spring Boot 2.7.x 集成 ElasticSearch7.x 全过程(二)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 如何对险岛尖兵属性加点 冒险岛尖兵属性加
- 下一篇: 魔兽世界7.25火法天赋加点 wow7.