SpringBoot 如何整合 ES 实现 CRUD 操作

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

本文为 spring-boot-examples 系列文章节选,示例代码已上传至 https://github.com/laolunsi/spring-boot-examples

安装 ES 与可视化工具

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

如此,表示 ES 安装完毕。

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

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

可能会出现如下情况:

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

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

刷新页面:

这里的 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 操作的资料请关注我们其它相关文章!

(0)

相关推荐

  • 详解springboot+mybatis-plue实现内置的CRUD使用详情

    mybatis-plus的特性 无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑 损耗小:启动即会自动注入基本CURD,性能基本无损耗,直接面向对象操作 强大的 CRUD操作:内置通用 Mapper.通用Service,仅仅通过少量配置即可实现单表大部分 CRUD 操作,更有强大的条件构造器,满足各类使用需求 支持 Lambda形式调用:通过 Lambda 表达式,方便的编写各类查询条件,无需再担心字段写错 支持主键自动生成:支持多达 4种主键策略(内含分布式唯一 ID 生成器

  • SpringBoot整合Mybatis实现CRUD

    准备工具:IDEA jdk1.8 Navicat for MySQL Postman 一.新建Project 选择依赖:mybatis Web Mysql JDBC 项目结构 pom依赖: <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.o

  • SpringBoot使用Spring-Data-Jpa实现CRUD操作

    本文演示了SpringBoot下,实用Spring-Data-Jpa来实现CRUD操作,视图层采用Freemarker 这里我们先把application.properties修改成application.yml 主流格式 内容也改成yml规范格式: server: port: 8888 context-path: / helloWorld: spring Boot\u4F60\u597D msyql: jdbcName: com.mysql.jdbc.Driver dbUrl: jdbc:my

  • SpringBoot 如何整合 ES 实现 CRUD 操作

    本文介绍 Spring Boot 项目中整合 ElasticSearch 并实现 CRUD 操作,包括分页.滚动等功能. 之前在公司使用 ES,一直用的是前辈封装好的包,最近希望能够从原生的 Spring Boot/ES 语法角度来学习 ES 的相关技术.希望对大家有所帮助. 本文为 spring-boot-examples 系列文章节选,示例代码已上传至 https://github.com/laolunsi/spring-boot-examples 安装 ES 与可视化工具 前往 ES 官方

  • Springboot和Jpa实现学生CRUD操作代码实例

    前期准备 使用idea新建个SpringBoot项目 在数据库中建student表 建包 1.编写entity包下实体类Student (一个Javabean规范) package com.example.stu.kudestu.stu.entity; import javax.persistence.*; @Entity @Table(name = "student") //@Entity 应用在实体类上 @Table(name = "student") 应用在实

  • IDEA+maven+SpringBoot+JPA+Thymeleaf实现Crud及分页

    一.开发环境: 1.windows 7 企业版 2.IDEA 14 3.JDK 1.8 4.Maven 3.5.2 5.MariaDB 6.SQLYog 二.Maven设置: Maven目录下的conf目录下的settings.xml做如下内容的添加: 1.使用阿里云的仓库,比官网访问速度快很多 <mirror> <id>nexus-aliyun</id> <mirrorOf>central</mirrorOf> <name>Nexu

  • SpringBoot整合Elasticsearch并实现CRUD操作

     配置准备 在build.gradle文件中添加如下依赖: compile "org.elasticsearch.client:transport:5.5.2" compile "org.elasticsearch:elasticsearch:5.5.2" //es 5.x的内部使用的 apache log4日志 compile "org.apache.logging.log4j:log4j-core:2.7" compile "org

  • Springboot整合MongoDB进行CRUD操作的两种方式(实例代码详解)

    1 简介 Springboot是最简单的使用Spring的方式,而MongoDB是最流行的NoSQL数据库.两者在分布式.微服务架构中使用率极高,本文将用实例介绍如何在Springboot中整合MongoDB的两种方法:MongoRepository和MongoTemplate. 代码结构如下: 2 项目准备 2.1 启动MongoDB实例 为了方便,使用Docker来启动MongoDB,详细指导文档请参考:基于Docker的MongoDB实现授权访问的方法,这里不再赘述. 2.2 引入相关依赖

  • Spring Boot整合Mybatis并完成CRUD操作的实现示例

    MyBatis 是一款优秀的持久层框架,被各大互联网公司使用,本文使用Spring Boot整合Mybatis,并完成CRUD操作. 为什么要使用Mybatis?我们需要掌握Mybatis吗? 说的官方一点: MyBatis 是一款优秀的持久层框架,它支持定制化 SQL.存储过程以及高级映射.MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集.MyBatis 可以使用简单的 XML 或注解来配置和映射原生信息,将接口和 Java 的 POJOs(Plain Ordina

  • springboot整合redis进行数据操作(推荐)

    redis是一种常见的nosql,日常开发中,我们使用它的频率比较高,因为它的多种数据接口,很多场景中我们都可以用到,并且redis对分布式这块做的非常好. springboot整合redis比较简单,并且使用redistemplate可以让我们更加方便的对数据进行操作. 1.添加依赖 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starte

  • Spring mvc整合mybatis(crud+分页插件)操作mysql

    一.web.xml配置 我们都知道java ee的项目启动的第一件事就是读取web.xml,spring mvc 的web.xml我在上一篇文章中也做了详细讲解,不懂的可以回头看看,讲解的这个项目源码我也会放到github上,也可以去那里看看,这里就不做介绍了. web.xml 配置 <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:/c

  • Spring boot整合Mybatis实现级联一对多CRUD操作的完整步骤

    前言 在关系型数据库中,随处可见表之间的连接,对级联的表进行增删改查也是程序员必备的基础技能.关于Spring Boot整合Mybatis在之前已经详细写过,不熟悉的可以回顾Spring Boot整合Mybatis并完成CRUD操作,这是本文操作的基础.本文先准备一个测试的数据库,然后使用MyBatis Generator进行部分代码自动生成,再以一个例子来展示稍微高级点的操作:使用Mybatis完成级联一对多的CRUD操作. 数据库准备 数据库用到三张表:user表,role表,user_ro

随机推荐