SpringBoot整合ES-Elasticsearch的实例
作者:融极 发布时间:2022-12-29 03:30:40
概述
本文介绍 Spring Boot 项目中整合 ElasticSearch 并实现 CRUD 操作,包括分页、滚动等功能。
添加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
创建索引对象
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数据的三种方式
实现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<ArticleEntity,String> {
}
文档操作
下面可以使用这个 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 {
@Resource
private ArticleRepository articleRepository;
/**
* 根据文档id查询数据
*
* @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";
}
}
分页查询与滚动查询
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 {
@Autowired
private ElasticsearchRestTemplate restTemplate;
@Autowired
private 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));
// 方法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();
}
/**
* 滚动查询
*
* @param scrollId 滚动id
* @param pageSize 分页大小
* @return 查询结果
*/
@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")) {
// 开启一个滚动查询,设置该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<ArticleEntity> 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<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 滚动查询
之前遇到的一个问题,日志检索的接口太慢了。
开始使用的是深度分页,即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;
@Autowired
private RestHighLevelClient client;
@Test
void contextLoads() throws IOException {
//创建索引请求
CreateIndexRequest request = new CreateIndexRequest("hong_index");
//客户端执行请求 IndicesClient create创建请求 RequestOptions.DEFAULT默认请求参数
CreateIndexResponse createIndexResponse = client.indices().create(request, RequestOptions.DEFAULT);
//获取返回的参数
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());
}
//测试添加文档
@Test
void test4() throws IOException {
//创建对象
User user = new User("枣信",18);
//创建索引库
IndexRequest request = new IndexRequest("hong_index");
//规则 为 put /hong_index/_doc/1
//创建的id
request.id("1");
//创建的时间
request.timeout(TimeValue.timeValueSeconds(1));
// request.timeout("1s");
//将数据放入到请求 JSON.toJSONString(user)将对象转换为json
request.source(JSON.toJSONString(user), XContentType.JSON);
//客户端发送请求 向索引中添加数据
IndexResponse indices = client.index(request, RequestOptions.DEFAULT);
//获取返回的json对象
System.out.println(indices.toString());
//获取发送请求的状态 添加为CREATED 更新为OK
System.out.println(indices.status());
}
//获取文档信息
@Test
void 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);
}
//更新 修改信息
@Test
void test7() throws IOException {
//根据索引库传入的id更新
UpdateRequest updateRequest = new UpdateRequest("hong_index","1");
//更新时间
updateRequest.timeout("1s");
//创建对象
User user = new User("李四", 26);
//更新 将对象转换为json
updateRequest.doc(JSON.toJSONString(user),XContentType.JSON);
//客户端发送请求,进行更新
UpdateResponse update = client.update(updateRequest, RequestOptions.DEFAULT);
//获取更新状态
System.out.println(update.status());
}
//删除文档信息
@Test
void test8() throws IOException {
//根据传入的索引id进行删除
DeleteRequest request = new DeleteRequest("hong_index","1");
//发送请求,删除
DeleteResponse delete = client.delete(request, RequestOptions.DEFAULT);
//获取删除的状态 没有删除成功为NOT_FOUND 删除成功为OK
System.out.println(delete.status());
}
//批量添加数据
@Test
void test9() throws IOException {
//创建批量添加
BulkRequest bulkRequest = new BulkRequest();
//添加时间
bulkRequest.timeout("8s");
//创建一个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));
//批量处理请求
for (int i = 0; i < userList.size(); i++) {
//批量更新和删除 在这修改对应的请求即可 不添加id(""+(i+1)) 会默认随机id,在大数据情况下,让他默认随机id
bulkRequest.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());
}
//查询索引信息
@Test
void 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());
}
}
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。
来源:https://blog.csdn.net/tianzhonghaoqing/article/details/122071875


猜你喜欢
- List和 java 中的 list 几乎一致;getOrElse 取出对应索引,如果该索引不存在或者为 null,就返回一个 lambda
- 以下是测试代码:新建一个classlibrary,包含两个类class1和class2,这两个类中分别有一个方法,都是返回一个字符串,代码如
- 在本次项目中,由于时间紧张直接使用DWR做Ajax请求!要求的是动态展示,那就需要使用DWR轮询请求,但是需要做异常情况下的一些处理!特别是
- 本文为大家分享了一个满足在线网页交流需求的实例,由于java Socket实现的网页版在线聊天功能,供大家参考,具体内容如下实现步骤:1、使
- 可重入锁 广义上的可重入锁指的是可重复可递归调用的锁,在外层使用锁之后,在内层仍然可以使用,并且不发生死锁(前提得是同一个对象或者
- 本文实例讲述了C#堆排序实现方法。分享给大家供大家参考。具体如下:private static void Adjust (int[] lis
- 挂起和恢复线程 Thread 的API中包含两个被淘汰的方法,它们用于临时挂起和重启某个线程,这些方法已
- 日常开发中,判断邮箱是少不了的,这个我以**C#**为例,来写一个判断方法,正则表达式是通用的,CV就可以首先引入正则需要使用的命名空间//
- 目前html5发展非常迅速,很多native app都会嵌入到网页中,以此来适用多变的市场需求。但是android的webview默认支持的
- 本文实例为大家分享了Android倒计时的开始与停止,剩余时分秒的展示效果,供大家参考,具体内容如下1.声明开启倒计时相关方法Handler
- 在C#的继承中尝尝会用到相关的修饰词:override和new。这两个修饰符都可以在新的子类中,重写同名的父类方法。
- 前言前面几篇我们简单的复习了一下自定义 View 的测量与绘制,并且回顾了常见的一些事件的处理方式。那么如果我们想自定义 ViewGroup
- 主要是应对这种需求:软件只允许启动一次。将这个问题转化一下,可以这样描述:对于一个软件,在启动一个进程之后,不允许启动其它进程,如果第二次打
- 本文为大家分享了C#实现图书管理系统课程设计,供大家参考,具体内容如下一、设计目的通过模拟图书管理系统,实现以下功能学生账号的注册学生对馆藏
- @schedule 注解 是springboot 常用的定时任务注解,使用起来简单方便,但是如果定时任务非常多,或者有的任务很耗时
- 前言app启动后的白屏问题,默认都是在splash页面加主题配置,主题配置一个背景来达到用户点击app图标就立马启动app的假象,大多情况下
- WebView2简介概述WebView2 全称 Microsoft Edge WebView2 控件,此控件的作用是在本机桌面应用中嵌入we
- 一、Kotlin 调用 Java1. kotlin 关键字转义java 中的方法或变量 是 kotlin 的关键字时,使用反引号 `` 对关
- IOS的滑动按钮菜单在UI设计里面绝对堪称一绝,在学习了Android的自定义view后,我萌生了模仿它的想法。实现上面的模拟需要自定义一个
- 一、前言近些年,随着微服务框架在越来越多的公司产品中实践落地,以Spring Cloud Alibaba为导向的一站式微服务解决方案也成为微