SpringBoot整合ES-Elasticsearch的實(shí)例
概述
本文介紹 Spring Boot 項(xiàng)目中整合 ElasticSearch 并實(shí)現(xiàn) CRUD 操作,包括分頁、滾動(dòng)等功能。
添加Maven依賴
<dependency> ? ? ? <groupId>org.springframework.boot</groupId> ? ? ? <artifactId>spring-boot-starter-data-elasticsearch</artifactId> </dependency>
配置application.yml
spring: ? elasticsearch: ? ? rest: ? ? ? uris: 192.168.1.81:9200
創(chuàng)建索引對(duì)象
package com.practice.elkstudy.entity; import cn.hutool.core.date.DateTime; import lombok.Data; import org.springframework.data.annotation.Id; import org.springframework.data.elasticsearch.annotations.Document; import java.util.Date; /** ?* @Description : 文檔模型 ?* @Version : V1.0.0 ?* @Date : 2021/12/22 14:08 ?*/ @Document(indexName = "article") @Data public class ArticleEntity { ? ? @Id ? ? private String id; ? ? private String title; ? ? private String content; ? ? private Integer userId; ? ? private Date createTime = DateTime.now(); }
SpringBoot操作ES數(shù)據(jù)的三種方式
- 實(shí)現(xiàn)ElasticsearchRepository接口
- 引入ElasticsearchRestTemplate
- 引入ElasticsearchOperations
實(shí)現(xiàn)索引對(duì)應(yīng)的Repository
package com.practice.elkstudy.repository; import com.practice.elkstudy.entity.ArticleEntity; import org.springframework.data.elasticsearch.repository.ElasticsearchRepository; /** ?* @Description : article數(shù)據(jù)操作接口 ?* @Version : V1.0.0 ?* @Date : 2021/12/22 14:18 ?*/ public interface ArticleRepository extends ElasticsearchRepository<ArticleEntity,String> { }
文檔操作
下面可以使用這個(gè) ArticleRepository 來操作 ES 中的 Article 數(shù)據(jù)。
我們這里沒有手動(dòng)創(chuàng)建這個(gè) Article 對(duì)應(yīng)的索引,由 elasticsearch 默認(rèn)生成。
下面的接口,實(shí)現(xiàn)了 spring boot 中對(duì) es 數(shù)據(jù)進(jìn)行插入、更新、分頁查詢、滾動(dòng)查詢、刪除等操作??梢宰鳛橐粋€(gè)參考。
其中,使用了 Repository 來獲取、保存、刪除 ES 數(shù)據(jù);使用 ElasticsearchRestTemplate 或 ElasticsearchOperations 來進(jìn)行分頁/滾動(dòng)查詢。
文檔保存、查詢、刪除
package com.practice.elkstudy.controller.controller; import com.practice.elkstudy.entity.ArticleEntity; import com.practice.elkstudy.repository.ArticleRepository; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; import java.util.Optional; /** * @Description : article控制類 * @Version : V1.0.0 * @Date : 2021/12/22 14:11 */ @RestController @RequestMapping("/elk") public class ArticleController { @Resource private ArticleRepository articleRepository; /** * 根據(jù)文檔id查詢數(shù)據(jù) * * @param id 文檔id * @return 文檔詳情 */ @GetMapping("/byId") public String findById(@RequestParam String id) { Optional<ArticleEntity> record = articleRepository.findById(id); return record.toString(); } /** * 保存文檔信息 * * @param article 文檔詳情 * @return 保存的文檔信息 */ @PostMapping("/saveArticle") public String saveArticle(@RequestBody ArticleEntity article) { ArticleEntity result = articleRepository.save(article); return result.toString(); } @DeleteMapping("/deleteById") public String deleteArticle(@RequestParam String id) { articleRepository.deleteById(id); return "success"; } }
分頁查詢與滾動(dòng)查詢
package com.practice.elkstudy.controller.controller; import com.practice.elkstudy.entity.ArticleEntity; import org.elasticsearch.index.query.BoolQueryBuilder; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.PageRequest; import org.springframework.data.elasticsearch.core.ElasticsearchOperations; import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate; import org.springframework.data.elasticsearch.core.SearchHit; import org.springframework.data.elasticsearch.core.SearchHits; import org.springframework.data.elasticsearch.core.SearchHitsImpl; import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates; import org.springframework.data.elasticsearch.core.query.NativeSearchQuery; import org.springframework.util.StringUtils; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.stream.Collectors; /** * @Description : article高級(jí)查詢 * @Version : V1.0.0 * @Date : 2021/12/22 15:10 */ @RestController @RequestMapping("/elk") public class ArticleAdvanceController { @Autowired private ElasticsearchRestTemplate restTemplate; @Autowired private ElasticsearchOperations operations; /** * 分頁查詢 * * @param pageNum 頁碼,從0開始 * @param pageSize 分頁大小 * @return 查詢結(jié)果 */ @GetMapping("/queryPage") public String queryPage(@RequestParam int pageNum, @RequestParam int pageSize) { NativeSearchQuery query = new NativeSearchQuery(new BoolQueryBuilder()); query.setPageable(PageRequest.of(pageNum, pageSize)); // 方法1 SearchHits<ArticleEntity> search = restTemplate.search(query, ArticleEntity.class); // 方法2 // SearchHits<ArticleEntity> search = operations.search(query, ArticleEntity.class); List<ArticleEntity> articles = search.getSearchHits().stream().map(SearchHit::getContent).collect(Collectors.toList()); return articles.toString(); } /** * 滾動(dòng)查詢 * * @param scrollId 滾動(dòng)id * @param pageSize 分頁大小 * @return 查詢結(jié)果 */ @GetMapping(value = "/scrollQuery") public String scroll(String scrollId, Integer pageSize) { if (pageSize == null || pageSize <= 0) { return "please input query page num"; } NativeSearchQuery query = new NativeSearchQuery(new BoolQueryBuilder()); query.setPageable(PageRequest.of(0, pageSize)); SearchHits<ArticleEntity> searchHits; if (StringUtils.isEmpty(scrollId) || scrollId.equals("0")) { // 開啟一個(gè)滾動(dòng)查詢,設(shè)置該scroll上下文存在60s // 同一個(gè)scroll上下文,只需要設(shè)置一次query(查詢條件) searchHits = restTemplate.searchScrollStart(60000, query, ArticleEntity.class, IndexCoordinates.of("article")); if (searchHits instanceof SearchHitsImpl) { scrollId = ((SearchHitsImpl) searchHits).getScrollId(); } } else { // 繼續(xù)滾動(dòng) searchHits = restTemplate.searchScrollContinue(scrollId, 60000, ArticleEntity.class, IndexCoordinates.of("article")); } List<ArticleEntity> articles = searchHits.getSearchHits().stream().map(SearchHit::getContent).collect(Collectors.toList()); if (articles.size() == 0) { // 結(jié)束滾動(dòng) restTemplate.searchScrollClear(Collections.singletonList(scrollId)); scrollId = null; } if (Objects.isNull(scrollId)) { Map<String, String> result = new HashMap<>(2); result.put("articles", articles.toString()); result.put("message", "已到末尾"); return result.toString(); } else { Map<String, String> result = new HashMap<>(); result.put("count", String.valueOf(searchHits.getTotalHits())); result.put("pageSize", String.valueOf(articles.size())); result.put("articles", articles.toString()); result.put("scrollId", scrollId); return result.toString(); } } }
ES深度分頁 vs 滾動(dòng)查詢
之前遇到的一個(gè)問題,日志檢索的接口太慢了。
開始使用的是深度分頁,即1,2,3…10,這樣的分頁查詢,查詢條件較多(十多個(gè)參數(shù))、查詢數(shù)據(jù)量較大(單個(gè)日志索引約2億條數(shù)據(jù))。
分頁查詢速度慢的原因在于:ES的分頁查詢,如查詢第100頁數(shù)據(jù),每頁10條,是先從每個(gè)分區(qū)(shard,一個(gè)索引默認(rèn)是5個(gè)shard)中把命中的前100*10條數(shù)據(jù)查出來,然后協(xié)調(diào)節(jié)點(diǎn)進(jìn)行合并操作,最后給出100頁的數(shù)據(jù)。也就是說,實(shí)際被加載到內(nèi)存的數(shù)據(jù)遠(yuǎn)遠(yuǎn)超過理想情況。
這樣,索引分片數(shù)越多,查詢頁數(shù)越多,查詢速度就越慢。ES默認(rèn)的max_result_window是10000條,也就是正常情況下,用分頁查詢到10000條數(shù)據(jù)時(shí),就不會(huì)在返回下一頁數(shù)據(jù)了。
如果不需要進(jìn)行跳頁,比如直接查詢第100頁數(shù)據(jù),或者數(shù)據(jù)量非常大,那么可以考慮用scroll查詢。在scroll查詢下,第1次需要根據(jù)查詢參數(shù)開啟一個(gè)scroll上下文,設(shè)置上下文緩存時(shí)間。以后的滾動(dòng)只需要根據(jù)第一次返回的scrollId來進(jìn)行即可。
scroll只支持往下滾動(dòng),如果想要往前滾動(dòng),還可以根據(jù)scrollId緩存查詢結(jié)果,這樣就可以實(shí)現(xiàn)上下文滾動(dòng)查詢了一一就像大家經(jīng)常使用的淘寶商品檢索時(shí)上下滾動(dòng)一樣。
SpringBoot集成ES基本使用
#配置es #Liunx 上的ip地址和配置端口號(hào) spring.elasticsearch.rest.uris=192.168.113.129:9200
在test中測(cè)試
import com.alibaba.fastjson.JSON; import com.hzx.pojo.User; import com.hzx.utils.ESconst; import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest; import org.elasticsearch.action.bulk.BulkRequest; import org.elasticsearch.action.bulk.BulkResponse; import org.elasticsearch.action.delete.DeleteRequest; import org.elasticsearch.action.delete.DeleteResponse; import org.elasticsearch.action.get.GetRequest; import org.elasticsearch.action.get.GetResponse; import org.elasticsearch.action.index.IndexRequest; import org.elasticsearch.action.index.IndexResponse; import org.elasticsearch.action.search.SearchRequest; import org.elasticsearch.action.search.SearchResponse; import org.elasticsearch.action.support.master.AcknowledgedResponse; import org.elasticsearch.action.update.UpdateRequest; import org.elasticsearch.action.update.UpdateResponse; import org.elasticsearch.client.RequestOptions; import org.elasticsearch.client.RestHighLevelClient; import org.elasticsearch.client.indices.CreateIndexRequest; import org.elasticsearch.client.indices.CreateIndexResponse; import org.elasticsearch.client.indices.GetIndexRequest; import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.index.query.QueryBuilders; import org.elasticsearch.index.query.TermQueryBuilder; import org.elasticsearch.search.SearchHit; import org.elasticsearch.search.builder.SearchSourceBuilder; import org.elasticsearch.search.fetch.subphase.FetchSourceContext; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import java.io.IOException; import java.util.ArrayList; import java.util.concurrent.TimeUnit; @Autowired private RestHighLevelClient client; @Test void contextLoads() throws IOException { //創(chuàng)建索引請(qǐng)求 CreateIndexRequest request = new CreateIndexRequest("hong_index"); //客戶端執(zhí)行請(qǐng)求 IndicesClient create創(chuàng)建請(qǐng)求 RequestOptions.DEFAULT默認(rèn)請(qǐng)求參數(shù) CreateIndexResponse createIndexResponse = client.indices().create(request, RequestOptions.DEFAULT); //獲取返回的參數(shù) System.out.println(createIndexResponse); } @Test void test2() throws IOException { //獲取指定索引庫 GetIndexRequest request = new GetIndexRequest("hong_index2"); //判斷獲取索引是否存在 boolean exists = client.indices().exists(request,RequestOptions.DEFAULT); //如果索引存在就返回為true 或者 為false System.out.println(exists); } @Test void test3() throws IOException { //刪除指定索引庫 DeleteIndexRequest request = new DeleteIndexRequest("hong_index"); //獲取刪除索引 AcknowledgedResponse delete = client.indices().delete(request, RequestOptions.DEFAULT); //檢查索引是否被刪除 System.out.println(delete.isAcknowledged()); } //測(cè)試添加文檔 @Test void test4() throws IOException { //創(chuàng)建對(duì)象 User user = new User("棗信",18); //創(chuàng)建索引庫 IndexRequest request = new IndexRequest("hong_index"); //規(guī)則 為 put /hong_index/_doc/1 //創(chuàng)建的id request.id("1"); //創(chuàng)建的時(shí)間 request.timeout(TimeValue.timeValueSeconds(1)); // request.timeout("1s"); //將數(shù)據(jù)放入到請(qǐng)求 JSON.toJSONString(user)將對(duì)象轉(zhuǎn)換為json request.source(JSON.toJSONString(user), XContentType.JSON); //客戶端發(fā)送請(qǐng)求 向索引中添加數(shù)據(jù) IndexResponse indices = client.index(request, RequestOptions.DEFAULT); //獲取返回的json對(duì)象 System.out.println(indices.toString()); //獲取發(fā)送請(qǐng)求的狀態(tài) 添加為CREATED 更新為OK System.out.println(indices.status()); } //獲取文檔信息 @Test void test6() throws IOException { //根據(jù)索引傳入的id獲取 GetRequest getRequest = new GetRequest("hong_index","1"); //通過get獲取信息 GetResponse getResponse = client.get(getRequest, RequestOptions.DEFAULT); //根據(jù)指定的Source獲取對(duì)應(yīng)內(nèi)容 System.out.println(getResponse.getSourceAsString()); //打印json對(duì)象 System.out.println(getResponse); } //更新 修改信息 @Test void test7() throws IOException { //根據(jù)索引庫傳入的id更新 UpdateRequest updateRequest = new UpdateRequest("hong_index","1"); //更新時(shí)間 updateRequest.timeout("1s"); //創(chuàng)建對(duì)象 User user = new User("李四", 26); //更新 將對(duì)象轉(zhuǎn)換為json updateRequest.doc(JSON.toJSONString(user),XContentType.JSON); //客戶端發(fā)送請(qǐng)求,進(jìn)行更新 UpdateResponse update = client.update(updateRequest, RequestOptions.DEFAULT); //獲取更新狀態(tài) System.out.println(update.status()); } //刪除文檔信息 @Test void test8() throws IOException { //根據(jù)傳入的索引id進(jìn)行刪除 DeleteRequest request = new DeleteRequest("hong_index","1"); //發(fā)送請(qǐng)求,刪除 DeleteResponse delete = client.delete(request, RequestOptions.DEFAULT); //獲取刪除的狀態(tài) 沒有刪除成功為NOT_FOUND 刪除成功為OK System.out.println(delete.status()); } //批量添加數(shù)據(jù) @Test void test9() throws IOException { //創(chuàng)建批量添加 BulkRequest bulkRequest = new BulkRequest(); //添加時(shí)間 bulkRequest.timeout("8s"); //創(chuàng)建一個(gè)arraylist集合 ArrayList<User> userList = new ArrayList<>(); userList.add(new User("李四",19)); userList.add(new User("王五",25)); userList.add(new User("趙剛",30)); userList.add(new User("張三",21)); userList.add(new User("趙六",36)); userList.add(new User("小武",20)); //批量處理請(qǐng)求 for (int i = 0; i < userList.size(); i++) { //批量更新和刪除 在這修改對(duì)應(yīng)的請(qǐng)求即可 不添加id(""+(i+1)) 會(huì)默認(rèn)隨機(jī)id,在大數(shù)據(jù)情況下,讓他默認(rèn)隨機(jī)id bulkRequest.add(new IndexRequest("hong_index").id(""+(i+1)).source(JSON.toJSONString(userList.get(i)),XContentType.JSON)); } //批量添加發(fā)送請(qǐng)求 BulkResponse bulk = client.bulk(bulkRequest, RequestOptions.DEFAULT); //獲取批量添加的狀態(tài) 返回false代表添加成功 System.out.println(bulk.hasFailures()); } //查詢索引信息 @Test void test10() throws IOException { //查詢 SearchRequest searchRequest = new SearchRequest(ESconst.ES_INDEX); //構(gòu)建搜索條件 SearchSourceBuilder sourceBuilder = new SearchSourceBuilder(); //查詢條件,可以使用QueryBuilders工具來實(shí)現(xiàn) // QueryBuilders.termQuery精確查詢 // QueryBuilders.matchQuery()查詢所有 TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("name", "李四"); //查詢的時(shí)間 sourceBuilder.timeout(new TimeValue(60, TimeUnit.SECONDS)); //將查詢的sourceBuilder放入searchRequest中 searchRequest.source(sourceBuilder); //發(fā)送請(qǐng)求 SearchResponse search = client.search(searchRequest, RequestOptions.DEFAULT); //獲取信息 System.out.println(JSON.toJSONString(search.getHits())); //循環(huán)變量出信息 for(SearchHit documentFields : search.getHits().getHits()){ //獲取所有信息 System.out.println(documentFields.getSourceAsMap()); } }
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
Spring?Security?中多個(gè)身份驗(yàn)證的示例代碼
這篇文章主要介紹了Spring?Security?中多個(gè)身份驗(yàn)證的示例代碼,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-09-09springboot使用ThreadPoolTaskExecutor多線程批量插入百萬級(jí)數(shù)據(jù)的實(shí)現(xiàn)方法
這篇文章主要介紹了springboot利用ThreadPoolTaskExecutor多線程批量插入百萬級(jí)數(shù)據(jù),本文通過示例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-02-02Java動(dòng)態(tài)代理四種實(shí)現(xiàn)方式詳解
這篇文章主要介紹了Java四種動(dòng)態(tài)代理實(shí)現(xiàn)方式,對(duì)于開始學(xué)習(xí)java動(dòng)態(tài)代理或者要復(fù)習(xí)java動(dòng)態(tài)代理的朋友來講很有參考價(jià)值,有感興趣的朋友可以參考一下2021-04-04Springboot實(shí)現(xiàn)自定義錯(cuò)誤頁面的方法(錯(cuò)誤處理機(jī)制)
這篇文章主要介紹了Springboot實(shí)現(xiàn)自定義錯(cuò)誤頁面的方法(錯(cuò)誤處理機(jī)制),本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-01-01Java發(fā)送form-data請(qǐng)求的實(shí)例代碼
在Java中發(fā)送form-data請(qǐng)求,可以使用Apache?HttpClient或OkHttp這樣的HTTP客戶端庫來發(fā)送請(qǐng)求,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2023-10-10