SpringBoot整合ES-Elasticsearch的实例

目录

  • 概述
  • 添加Maven依赖
  • 配置application.yml
  • 创建索引对象
  • SpringBoot操作ES数据的三种方式
    • 实现索引对应的Repository
  • 文档操作
    • 文档保存、查询、删除
  • 分页查询与滚动查询
    • ES深度分页 vs 滚动查询
      • SpringBoot集成ES基本使用
        • 在test中测试

      概述 本文介绍 Spring Boot 项目中整合 ElasticSearch 并实现 CRUD 操作,包括分页、滚动等功能。

      添加Maven依赖
      org.springframework.bootspring-boot-starter-data-elasticsearch


      配置application.yml
      spring:elasticsearch:rest:uris: 192.168.1.81:9200


      创建索引对象
      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")@Datapublic class ArticleEntity {@Idprivate String id; private String title; private String content; private Integer userId; private Date createTime = DateTime.now(); }


      SpringBoot操作ES数据的三种方式
      • 实现ElasticsearchRepository接口
      • 引入ElasticsearchRestTemplate
      • 引入ElasticsearchOperations

      实现索引对应的Repository
      package com.practice.elkstudy.repository; import com.practice.elkstudy.entity.ArticleEntity; import org.springframework.data.elasticsearch.repository.ElasticsearchRepository; /** * @Description : article数据操作接口 * @Version : V1.0.0 * @Date : 2021/12/22 14:18 */public interface ArticleRepository extends ElasticsearchRepository {}


      文档操作 下面可以使用这个 ArticleRepository 来操作 ES 中的 Article 数据。
      我们这里没有手动创建这个 Article 对应的索引,由 elasticsearch 默认生成。
      下面的接口,实现了 spring boot 中对 es 数据进行插入、更新、分页查询、滚动查询、删除等操作。可以作为一个参考。
      其中,使用了 Repository 来获取、保存、删除 ES 数据;使用 ElasticsearchRestTemplate 或 ElasticsearchOperations 来进行分页/滚动查询。

      文档保存、查询、删除
      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 {@Resourceprivate ArticleRepository articleRepository; /*** 根据文档id查询数据** @param id 文档id* @return 文档详情*/@GetMapping("/byId")public String findById(@RequestParam String id) {Optional record = articleRepository.findById(id); returnrecord.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"; }}


      分页查询与滚动查询
      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高级查询 * @Version : V1.0.0 * @Date : 2021/12/22 15:10 */@RestController@RequestMapping("/elk")public class ArticleAdvanceController {@Autowiredprivate ElasticsearchRestTemplate restTemplate; @Autowiredprivate ElasticsearchOperations operations; /*** 分页查询** @param pageNum页码,从0开始* @param pageSize 分页大小* @return 查询结果*/@GetMapping("/queryPage")public String queryPage(@RequestParam int pageNum, @RequestParam int pageSize) {NativeSearchQuery query = new NativeSearchQuery(new BoolQueryBuilder()); query.setPageable(PageRequest.of(pageNum, pageSize)); // 方法1SearchHits search = restTemplate.search(query, ArticleEntity.class); // 方法2// SearchHits search = operations.search(query, ArticleEntity.class); List articles = search.getSearchHits().stream().map(SearchHit::getContent).collect(Collectors.toList()); return articles.toString(); }/*** 滚动查询** @param scrollId 滚动id* @param pageSize 分页大小* @return 查询结果*/@GetMapping(value = "https://www.it610.com/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 searchHits; if (StringUtils.isEmpty(scrollId) || scrollId.equals("0")) {// 开启一个滚动查询,设置该scroll上下文存在60s// 同一个scroll上下文,只需要设置一次query(查询条件)searchHits = restTemplate.searchScrollStart(60000, query, ArticleEntity.class, IndexCoordinates.of("article")); if (searchHits instanceof SearchHitsImpl) {scrollId = ((SearchHitsImpl) searchHits).getScrollId(); }} else {// 继续滚动searchHits = restTemplate.searchScrollContinue(scrollId, 60000, ArticleEntity.class, IndexCoordinates.of("article")); }List articles = searchHits.getSearchHits().stream().map(SearchHit::getContent).collect(Collectors.toList()); if (articles.size() == 0) {// 结束滚动restTemplate.searchScrollClear(Collections.singletonList(scrollId)); scrollId = null; }if (Objects.isNull(scrollId)) {Map result = new HashMap<>(2); result.put("articles", articles.toString()); result.put("message", "已到末尾"); return result.toString(); } else {Map 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 滚动查询 之前遇到的一个问题,日志检索的接口太慢了。
      开始使用的是深度分页,即1,2,3…10,这样的分页查询,查询条件较多(十多个参数)、查询数据量较大(单个日志索引约2亿条数据)。
      分页查询速度慢的原因在于:ES的分页查询,如查询第100页数据,每页10条,是先从每个分区(shard,一个索引默认是5个shard)中把命中的前100*10条数据查出来,然后协调节点进行合并操作,最后给出100页的数据。也就是说,实际被加载到内存的数据远远超过理想情况。
      这样,索引分片数越多,查询页数越多,查询速度就越慢。ES默认的max_result_window是10000条,也就是正常情况下,用分页查询到10000条数据时,就不会在返回下一页数据了。
      如果不需要进行跳页,比如直接查询第100页数据,或者数据量非常大,那么可以考虑用scroll查询。在scroll查询下,第1次需要根据查询参数开启一个scroll上下文,设置上下文缓存时间。以后的滚动只需要根据第一次返回的scrollId来进行即可。
      scroll只支持往下滚动,如果想要往前滚动,还可以根据scrollId缓存查询结果,这样就可以实现上下文滚动查询了一一就像大家经常使用的淘宝商品检索时上下滚动一样。

      SpringBoot集成ES基本使用
      #配置es#Liunx 上的ip地址和配置端口号spring.elasticsearch.rest.uris=192.168.113.129:9200


      在test中测试
      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; @Autowiredprivate RestHighLevelClient client; @Testvoid contextLoads() throws IOException {//创建索引请求CreateIndexRequest request = new CreateIndexRequest("hong_index"); //客户端执行请求 IndicesClientcreate创建请求RequestOptions.DEFAULT默认请求参数CreateIndexResponse createIndexResponse = client.indices().create(request, RequestOptions.DEFAULT); //获取返回的参数System.out.println(createIndexResponse); }@Testvoid test2() throws IOException {//获取指定索引库GetIndexRequest request = new GetIndexRequest("hong_index2"); //判断获取索引是否存在boolean exists = client.indices().exists(request,RequestOptions.DEFAULT); //如果索引存在就返回为true或者 为falseSystem.out.println(exists); }@Testvoid test3() throws IOException {//删除指定索引库DeleteIndexRequest request = new DeleteIndexRequest("hong_index"); //获取删除索引AcknowledgedResponse delete = client.indices().delete(request, RequestOptions.DEFAULT); //检查索引是否被删除System.out.println(delete.isAcknowledged()); }//测试添加文档@Testvoid test4() throws IOException {//创建对象User user = new User("枣信",18); //创建索引库IndexRequest request = new IndexRequest("hong_index"); //规则 为 put /hong_index/_doc/1//创建的idrequest.id("1"); //创建的时间request.timeout(TimeValue.timeValueSeconds(1)); //request.timeout("1s"); //将数据放入到请求JSON.toJSONString(user)将对象转换为jsonrequest.source(JSON.toJSONString(user), XContentType.JSON); //客户端发送请求向索引中添加数据IndexResponse indices = client.index(request, RequestOptions.DEFAULT); //获取返回的json对象System.out.println(indices.toString()); //获取发送请求的状态 添加为CREATED更新为OKSystem.out.println(indices.status()); }//获取文档信息@Testvoid test6() throws IOException {//根据索引传入的id获取GetRequest getRequest = new GetRequest("hong_index","1"); //通过get获取信息GetResponse getResponse = client.get(getRequest, RequestOptions.DEFAULT); //根据指定的Source获取对应内容System.out.println(getResponse.getSourceAsString()); //打印json对象System.out.println(getResponse); }//更新 修改信息@Testvoid test7() throws IOException {//根据索引库传入的id更新UpdateRequest updateRequest = new UpdateRequest("hong_index","1"); //更新时间updateRequest.timeout("1s"); //创建对象User user = new User("李四", 26); //更新将对象转换为jsonupdateRequest.doc(JSON.toJSONString(user),XContentType.JSON); //客户端发送请求,进行更新UpdateResponse update = client.update(updateRequest, RequestOptions.DEFAULT); //获取更新状态System.out.println(update.status()); }//删除文档信息@Testvoid test8() throws IOException {//根据传入的索引id进行删除DeleteRequest request = new DeleteRequest("hong_index","1"); //发送请求,删除DeleteResponse delete = client.delete(request, RequestOptions.DEFAULT); //获取删除的状态没有删除成功为NOT_FOUND 删除成功为OKSystem.out.println(delete.status()); }//批量添加数据@Testvoid test9() throws IOException {//创建批量添加BulkRequest bulkRequest = new BulkRequest(); //添加时间bulkRequest.timeout("8s"); //创建一个arraylist集合ArrayList 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)); //批量处理请求for (int i = 0; i < userList.size(); i++) {//批量更新和删除 在这修改对应的请求即可不添加id(""+(i+1)) 会默认随机id,在大数据情况下,让他默认随机idbulkRequest.add(new IndexRequest("hong_index").id(""+(i+1)).source(JSON.toJSONString(userList.get(i)),XContentType.JSON)); }//批量添加发送请求BulkResponse bulk = client.bulk(bulkRequest, RequestOptions.DEFAULT); //获取批量添加的状态 返回false代表添加成功System.out.println(bulk.hasFailures()); }//查询索引信息@Testvoid test10() throws IOException {//查询SearchRequest searchRequest = new SearchRequest(ESconst.ES_INDEX); //构建搜索条件SearchSourceBuilder sourceBuilder = new SearchSourceBuilder(); //查询条件,可以使用QueryBuilders工具来实现// QueryBuilders.termQuery精确查询// QueryBuilders.matchQuery()查询所有TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("name", "李四"); //查询的时间sourceBuilder.timeout(new TimeValue(60, TimeUnit.SECONDS)); //将查询的sourceBuilder放入searchRequest中searchRequest.source(sourceBuilder); //发送请求SearchResponse search = client.search(searchRequest, RequestOptions.DEFAULT); //获取信息System.out.println(JSON.toJSONString(search.getHits())); //循环变量出信息for(SearchHit documentFields : search.getHits().getHits()){//获取所有信息System.out.println(documentFields.getSourceAsMap()); }}

      【SpringBoot整合ES-Elasticsearch的实例】以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

        推荐阅读