欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

SpringBoot 如何整合 ES 实现 CRUD 操作

程序员文章站 2022-03-27 20:12:38
本文介绍 spring boot 项目中整合 elasticsearch 并实现 crud 操作,包括分页、滚动等功能。之前在公司使用 es,一直用的是前辈封装好的包,最近希望能够从原生的 sprin...

本文介绍 spring boot 项目中整合 elasticsearch 并实现 crud 操作,包括分页、滚动等功能。
之前在公司使用 es,一直用的是前辈封装好的包,最近希望能够从原生的 spring boot/es 语法角度来学习 es 的相关技术。希望对大家有所帮助。

本文为 spring-boot-examples 系列文章节选,示例代码已上传至

安装 es 与可视化工具

前往 es 官方 进行,如 windows 版本只需要下载安装包,启动 elasticsearch.bat 文件,浏览器访问 http://localhost:9200

SpringBoot 如何整合 ES 实现 CRUD 操作

如此,表示 es 安装完毕。

为更好地查看 es 数据,再安装一下 elasticsearch-head 可视化插件。前往下载地址:
主要步骤:

  • git clone git://github.com/mobz/elasticsearch-head.git
  • cd elasticsearch-head
  • npm install
  • npm run start
  • open http://localhost:9100/

可能会出现如下情况:

SpringBoot 如何整合 ES 实现 CRUD 操作

发现是跨域的问题。
解决办法是在 elasticsearch 的 config 文件夹中的 elasticsearch.yml 中添加如下两行配置:

http.cors.enabled: true
http.cors.allow-origin: "*"

刷新页面:

SpringBoot 如何整合 ES 实现 CRUD 操作

这里的 article 索引就是我通过 spring boot 项目自动创建的索引。
下面我们进入正题。

spring boot 引入 es

创建一个 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

创建一个测试的对象,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 数据的三种方式:

  • 实现 elasticsearchrepository 接口
  • 引入 elasticsearchresttemplate
  • 引入 elasticsearchoperations

实现对应的 repository:

import org.springframework.data.elasticsearch.repository.elasticsearchrepository;

public interface articlerepository extends elasticsearchrepository<article, string> {

}

下面可以使用这个 articlerepository 来操作 es 中的 article 数据。
我们这里没有手动创建这个 article 对应的索引,由 elasticsearch 默认生成。

下面的接口,实现了 spring boot 中对 es 数据进行插入、更新、分页查询、滚动查询、删除等操作。可以作为一个参考。其中,使用了 repository 来获取、保存、删除 es 数据,使用 elasticsearchresttemplate 或 elasticsearchoperations 来进行分页/滚动查询。

根据 id 获取/删除数据

 @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) {
 // 根据 id 删除
 articlerepository.deletebyid(id);
 return new jsonresult(true, "删除成功");
 }

保存数据

 @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 "标题不能为空";
 } else if (stringutils.isempty(article.getcontent())) {
  return "内容不能为空";
 }

 return null;
 }

分页查询数据

 @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, "请输入合法的分页参数");
 }
 // 分页列表查询
 // 旧版本的 repository 中的 search 方法被废弃了。
 // 这里采用 elasticsearchresttemplate 或 elasticsearchoperations 来进行分页查询

 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;
 }

滚动查询数据

 @getmapping("scroll")
 public jsonresult scroll(string scrollid, integer size) {
 // 滚动查询 scroll api
 if (size == null || size <= 0) {
  return new jsonresult(false, "请输入每页查询数");
 }
 nativesearchquery query = new nativesearchquery(new boolquerybuilder());
 query.setpageable(pagerequest.of(0, size));
 searchhits<article> searchhits = null;
 if (stringutils.isempty(scrollid)) {
  // 开启一个滚动查询,设置该 scroll 上下文存在 60s
  // 同一个 scroll 上下文,只需要设置一次 query(查询条件)
  searchhits = elasticsearchresttemplate.searchscrollstart(60000, query, article.class, indexcoordinates.of("article"));
  if (searchhits instanceof searchhitsimpl) {
  scrollid = ((searchhitsimpl) searchhits).getscrollid();
  }
 } else {
  // 继续滚动
  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) {
  // 结束滚动
  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 滚动查询

上次遇到一个问题,同事跟我说日志检索的接口太慢了,问我能不能优化一下。开始使用的是深度分页,即 1,2,3..10, 这样的分页查询,查询条件较多(十多个参数)、查询数据量较大(单个日志索引约 2 亿条数据)。

分页查询速度慢的原因在于:es 的分页查询,如查询第 100 页数据,每页 10 条,是先从每个分区 (shard,一个索引默认是 5 个 shard) 中把命中的前 100 * 10 条数据查出来,然后由协调节点进行合并等操作,最后给出第 100 页的数据。也就是说,实际被加载到内存中的数据远超过理想情况。

这样,索引的 shard 越大,查询页数越多,查询速度就越慢。
es 默认的 max_result_window 是 10000 条,也就是正常情况下,用分页查询到 10000 条数据时,就不会再返回下一页数据了。

如果不需要进行跳页,比如直接查询第 100 页数据,或者数据量非常大,那么可以考虑用 scroll 查询。
在 scroll 查询下,第一次需要根据查询参数开启一个 scroll 上下文,设置上下文缓存时间。以后的滚动只需要根据第一次返回的 scrollid 来进行即可。

scroll 只支持往下滚动,如果想要往回滚动,还可以根据 scrollid 缓存查询结果,这样就可以实现上下滚动查询了 —— 就像大家经常使用的淘宝商品检索时上下滚动一样。

以上就是springboot 如何整合 es 实现 crud 操作的详细内容,更多关于springboot实现 crud 操作的资料请关注其它相关文章!