当前位置: 首页 > news >正文

elasticsearch_Spring Boot 整合 Elasticsearch

6478461ada7c472c3f34759ca5dae5b5.gif

学习在 Spring Boot 中使用 Elasticsearch。在 Spring Boot 中,使用的 Elasticsearch 实际上是 Spring Data Elasticsearch , Spring Data 是 Spring 家族的一个子项目,用于简化 SQL 和 NoSQL 的访问,在 Spring Data 中,只要你的方法名称符合规范,它就知道你想干什么,不需要自己再去写 SQL 。

1 概述

1.1 简介

Elasticsearch 是一个基于 Lucene 的搜索服务器。它提供了一个分布式的全文搜索引擎,基于 restful web 接口。Elasticsearch 是用 Java 语言开发的,基于 Apache 协议的开源项目,是目前最受欢迎的企业搜索引擎。Elasticsearch 广泛运用于云计算中,能够达到实时搜索,具有稳定,可靠,快速的特点。

1.2 基本概念

  • Near Realtime(近实时):Elasticsearch 是一个近乎实时的搜索平台,这意味着从索引文档到可搜索文档之间只有一个轻微的延迟(通常是一秒钟)。
  • Cluster(集群):集群是一个或多个节点的集合,它们一起保存整个数据,并提供跨所有节点的联合索引和搜索功能。每个集群都有自己的唯一集群名称,节点通过名称加入集群。
  • Node(节点):节点是指属于集群的单个 Elasticsearch 实例,存储数据并参与集群的索引和搜索功能。可以将节点配置为按集群名称加入特定集群,默认情况下,每个节点都设置为加入一个名为 elasticsearch 的集群。
  • Index(索引):索引是一些具有相似特征的文档集合,类似于 MySQL 中数据库的概念
  • Type(类型):类型是索引的逻辑类别分区,通常,为具有一组公共字段的文档类型,类似于 MySQL 中表的概念。注意:由于一些原因,在 Elasticsearch 6.0 以后,一个 Index 只能含有一个 Type。这其中的原因是:相同 index 的不同映射 type 中具有相同名称的字段是相同;在 Elasticsearch 索引中,不同映射 type 中具有相同名称的字段在 Lucene 中被同一个字段支持。在默认的情况下是 _doc 。在未来 8.0 的版本中,type 将被彻底删除。
  • Document(文档):文档是可被索引的基本信息单位,以 JSON 形式表示,类似于 MySQL 中行的概念
  • Field(字段):类似于 MySQL 中列的概念
  • Shards(分片):当索引存储大量数据时,可能会超出单个节点的硬件限制,为了解决这个问题,Elasticsearch 提供了将索引细分为分片的概念。分片机制赋予了索引水平扩容的能力,并允许跨分片分发和并行化操作,从而提高性能和吞吐量。
  • Replicas(副本):在可能出现故障的网络环境中,需要有一个故障切换机制,Elasticsearch 提供了将索引的分片复制为一个或多个副本的功能,副本在某些节点失效的情况下提供高可用性。

Elasticsearch数据库
索引 Index数据库 Database
类型 Type表 Table
文档 Document行 Row
字段 Field列 Column

1.3 常用命令

  • 查看集群健康状态:GET /_cat/health?v
  • 查看节点状态:GET /_cat/nodes?v
  • 查看所有索引信息:GET /_cat/indices?v

2 创建工程并配置

创建 Spring Boot 项目 spring-boot-elasticsearch ,添加 Web/Elasticsearch 依赖,如下:

57ef48c494df2a4655a84e1f89db6b97.png

最终的依赖如下:

<dependencies>
    <dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-data-elasticsearchartifactId>
    dependency>
    <dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-webartifactId>
    dependency>
    <dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-testartifactId>
        <scope>testscope>
        <exclusions>
            <exclusion>
                <groupId>org.junit.vintagegroupId>
                <artifactId>junit-vintage-engineartifactId>
            exclusion>
        exclusions>
    dependency>
dependencies>

接着在 application.properties 配置文件中添加 Elasticsearch 的基本配置,如下:

spring.data.elasticsearch.repositories.enabled=true

spring.elasticsearch.rest.uris=http://localhost:9200
spring.elasticsearch.rest.username=elastic
spring.elasticsearch.rest.password=000000

3 使用

首先创建一个 EsUser 实体类,如下:

@Document(indexName = "user", shards = 1, replicas = 0)
public class EsUser implements Serializable {
    private static final long serialVersionUID = -1L;
    @Id
    private Integer id;
    @Field(type = FieldType.Text, analyzer = "ik_max_word", searchAnalyzer = "ik_smart")
    private String username;
    @Field(type = FieldType.Keyword)
    private String password;
    @Field(type = FieldType.Boolean)
    private Boolean enabled = true;
    @Field(type = FieldType.Boolean)
    private Boolean locked = false;
    @Field(type = FieldType.Text, analyzer = "ik_max_word", searchAnalyzer = "ik_smart")
    private String address;
    @Field(type = FieldType.Text, analyzer = "ik_max_word", searchAnalyzer = "ik_smart")
    private String nickName;
    // @Field(type = FieldType.Date, format = DateFormat.date_hour_minute_second)
    // @JsonFormat(shape = JsonFormat.Shape.STRING, pattern ="yyyy-MM-dd'T'HH:mm:ss")
    private Date createTime;
    // @Field(type = FieldType.Date, format = DateFormat.date_hour_minute_second)
    // @JsonFormat(shape = JsonFormat.Shape.STRING, pattern ="yyyy-MM-dd'T'HH:mm:ss")
    private Date updateTime;

    // getter/setter
}

实体类说明:

  • @Document 注解: 标识映射到 Elasticsearch 文档上的领域对象。
  • @Id 注解: 标识文档的 id 。
  • @Field 注解: 标识字段,可以指定各字段的类型、分析器等,最终会体现在对应 indexmappings 上。类型如下:
public enum FieldType {
    Auto, // 自动判断,默认值
    Text, // 会进行分词
    Keyword, // 不会进行分词
    Long,
    Integer,
    Short,
    Byte,
    Double,
    Float,
    Half_Float,
    Scaled_Float,
    Date,
    Date_Nanos,
    Boolean,
    Binary,
    Integer_Range,
    Float_Range,
    Long_Range,
    Double_Range,
    Date_Range,
    Ip_Range,
    Object,
    Nested, // 嵌套对象
    Ip,
    TokenCount,
    Percolator,
    Flattened,
    Search_As_You_Type;

    private FieldType() {
    }
}

下面开始定义接口操作 Elasticsearch ,新增 EsUserRepository ,定义相关接口,如下:

public interface EsUserRepository extends ElasticsearchRepository<EsUser, Integer> {
    // 方法定义规范:
    // 1.按照 Spring Data 的规范,查询方法以 find/get/read 开头
    // 2.涉及条件查询时,条件的属性用条件关键字连接,要注意的是:条件属性以首字母大写
    // 3.支持属性的级联查询. 若当前类有符合条件的属性, 则优先使用, 而不使用级联属性. 若需要使用级联属性, 则属性之间使用 _ 进行连接

    EsUser findByIdAndUsername(Integer id, String username);

    Page findByIdGreaterThan(Integer id, Pageable pageable);

    List findByIdLessThanOrUsernameContaining(Integer id, String username);

    // 使用 @Query 注解可以用 Elasticsearch 的 DSL 语句进行查询
    @Query("{\"bool\" : {\"must\" : {\"match\" : {\"address\" : \"?0\"}}}}")
    Page findByAddress(String address, Pageable pageable);
}

接口类说明:

  • EsUserRepository 接口继承自 ElasticsearchRepository , ElasticsearchRepository 提供了一些基本的数据操作方法,例如:保存/更新/删除/列表查询/分页列表查询等。
  • EsUserRepository 接口中也可以自己声明相关的方法,只需要方法名称符合规范。在 Spring Data 中,只要按照既定的规范命名方法,Spring Data Elasticsearch 就知道你想干嘛,这样就不用写 DSL 语句了。相关规范参考下图:
c642d3a683e69d7146a6cb2da3c15412.png
  • 如果有特殊的查询,也可以自己定义方法名,使用 @Query 注解通过自定义 DSL 语句来实现。

这时启动 Spring Boot 项目,会自动创建一个名为 user 的索引。

4 测试

最后在测试类中注入 esUserRepository 完成测试,如下:

@SpringBootTest
class SpringBootElasticsearchApplicationTests {

    @Autowired
    EsUserRepository esUserRepository;
    // 用于自定义复杂查询
    @Autowired
    private ElasticsearchRestTemplate elasticsearchRestTemplate;

    @Test
    public void save() {
        EsUser esUser = new EsUser();
        esUser.setId(1);
        esUser.setUsername("zhangsan");
        esUser.setPassword("123456");
        esUser.setAddress("浙江杭州");
        esUser.setNickName("张三");
        esUser.setCreateTime(new Date());
        esUser.setUpdateTime(new Date());
        EsUser esUserResult = esUserRepository.save(esUser);
        System.out.println(esUserResult);
    }

    @Test
    public void saveAll() {
        List esUserList = new ArrayList<>();for (int i = 2; i <= 11; i++) {
            EsUser esUser = new EsUser();
            esUser.setId(i);
            esUser.setUsername("lisi" + i);
            esUser.setPassword("123456");
            esUser.setAddress("浙江宁波");
            esUser.setNickName("李四" + i);
            esUser.setCreateTime(new Date());
            esUser.setUpdateTime(new Date());
            esUserList.add(esUser);
        }
        Iterable list = esUserRepository.saveAll(esUserList);
        System.out.println(list);
    }@Testpublic void deleteById() {
        esUserRepository.deleteById(5);
    }@Testpublic void delete() {
        EsUser esUser = new EsUser();
        esUser.setId(6);
        esUserRepository.delete(esUser);
    }@Testpublic void deleteAll() {
        esUserRepository.deleteAll();
    }@Testpublic void findById() {
        Optional esUser = esUserRepository.findById(1);
        System.out.println(esUser.get());
    }@Testpublic void findAllById() {
        List idList = new ArrayList<>();
        idList.add(1);
        idList.add(2);
        Iterable list = esUserRepository.findAllById(idList);
        System.out.println(list);
    }@Testpublic void findAll() {
        Iterable list = esUserRepository.findAll();
        System.out.println(list);
    }@Testpublic void findAllSort() {
        Iterable list = esUserRepository.findAll(Sort.by(Sort.Direction.DESC, "id"));
        System.out.println(list);
    }@Testpublic void findAllPage() {
        Pageable pageable = PageRequest.of(0, 2);
        Page page = esUserRepository.findAll(pageable);
        System.out.println("总记录数:" + page.getTotalElements());
        System.out.println("当前页记录数:" + page.getNumberOfElements());
        System.out.println("每页记录数:" + page.getSize());
        System.out.println("总页数:" + page.getTotalPages());
        System.out.println("查询结果:" + page.getContent());
        System.out.println("当前页(从0开始计):" + page.getNumber());
        System.out.println("是否为首页:" + page.isFirst());
        System.out.println("是否为尾页:" + page.isLast());
    }/**
     * more_like_this query
     */@Testpublic void searchSimilar() {
        EsUser esUser = new EsUser();
        esUser.setId(2);
        Pageable pageable = PageRequest.of(0, 2);
        Page page = esUserRepository.searchSimilar(esUser, new String[]{"address"}, pageable);
        System.out.println("总记录数:" + page.getTotalElements());
        System.out.println("当前页记录数:" + page.getNumberOfElements());
        System.out.println("每页记录数:" + page.getSize());
        System.out.println("总页数:" + page.getTotalPages());
        System.out.println("查询结果:" + page.getContent());
        System.out.println("当前页(从0开始计):" + page.getNumber());
        System.out.println("是否为首页:" + page.isFirst());
        System.out.println("是否为尾页:" + page.isLast());
    }@Testpublic void existsById() {boolean b = esUserRepository.existsById(1);
        System.out.println(b);
    }@Testpublic void count() {long count = esUserRepository.count();
        System.out.println(count);
    }/*=============== 自定义简单查询-开始 ===============*/@Testpublic void findByIdAndUsername() {
        EsUser esUser = esUserRepository.findByIdAndUsername(2, "lisi");
        System.out.println(esUser);
    }@Testpublic void findByIdGreaterThan() {
        Pageable pageable = PageRequest.of(0, 2);
        Page page = esUserRepository.findByIdGreaterThan(4, pageable);
        System.out.println("总记录数:" + page.getTotalElements());
        System.out.println("当前页记录数:" + page.getNumberOfElements());
        System.out.println("每页记录数:" + page.getSize());
        System.out.println("总页数:" + page.getTotalPages());
        System.out.println("查询结果:" + page.getContent());
        System.out.println("当前页(从0开始计):" + page.getNumber());
        System.out.println("是否为首页:" + page.isFirst());
        System.out.println("是否为尾页:" + page.isLast());
    }@Testpublic void findByIdLessThanOrUsernameContaining() {
        List list = esUserRepository.findByIdLessThanOrUsernameContaining(10, "si");
        System.out.println(list);
    }@Testpublic void findByAddress() {
        Pageable pageable = PageRequest.of(0, 2);
        Page page = esUserRepository.findByAddress("宁波", pageable);
        System.out.println("总记录数:" + page.getTotalElements());
        System.out.println("当前页记录数:" + page.getNumberOfElements());
        System.out.println("每页记录数:" + page.getSize());
        System.out.println("总页数:" + page.getTotalPages());
        System.out.println("查询结果:" + page.getContent());
        System.out.println("当前页(从0开始计):" + page.getNumber());
        System.out.println("是否为首页:" + page.isFirst());
        System.out.println("是否为尾页:" + page.isLast());
    }/*=============== 自定义简单查询-结束 ===============*//*=============== 自定义复杂查询(ElasticsearchRestTemplate)-开始 ===============*//**
     * 根据关键字搜索用户名或昵称,再增加过滤、聚合、排序、分页
     */@Testpublic void search() {
        Boolean enabled = true;
        Boolean locked = false;
        String keyword = "四";
        Integer sort = 1;
        PageImpl page = null;
        NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();// 搜索if (StringUtils.isEmpty(keyword)) {
            nativeSearchQueryBuilder.withQuery(QueryBuilders.matchAllQuery());
        } else {// nativeSearchQueryBuilder.withQuery(QueryBuilders.multiMatchQuery(keyword, "username", "nickName"));
            List filterFunctionBuilders = new ArrayList<>();
            filterFunctionBuilders.add(new FunctionScoreQueryBuilder.FilterFunctionBuilder(QueryBuilders.matchQuery("username", keyword),
                    ScoreFunctionBuilders.weightFactorFunction(10)));
            filterFunctionBuilders.add(new FunctionScoreQueryBuilder.FilterFunctionBuilder(QueryBuilders.matchQuery("nickName", keyword),
                    ScoreFunctionBuilders.weightFactorFunction(2)));
            FunctionScoreQueryBuilder.FilterFunctionBuilder[] builders = new FunctionScoreQueryBuilder.FilterFunctionBuilder[filterFunctionBuilders.size()];
            filterFunctionBuilders.toArray(builders);
            FunctionScoreQueryBuilder functionScoreQueryBuilder = QueryBuilders.functionScoreQuery(builders)
                    .scoreMode(FunctionScoreQuery.ScoreMode.SUM)
                    .setMinScore(2);
            nativeSearchQueryBuilder.withQuery(functionScoreQueryBuilder);
        }// 过滤if (enabled != null || locked != null) {
            BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();if (enabled != null) {
                boolQueryBuilder.must(QueryBuilders.termQuery("enabled", enabled));
            }if (locked != null) {
                boolQueryBuilder.must(QueryBuilders.termQuery("locked", locked));
            }
            nativeSearchQueryBuilder.withFilter(boolQueryBuilder);
        }// 聚合,对应的字段一般设置为 FieldType.Keyword// nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms("passwords").field("password"));// nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms("addresses").field("address"));// 排序if (sort == 1) {// 按id降序
            nativeSearchQueryBuilder.withSort(SortBuilders.fieldSort("id").order(SortOrder.DESC));
        } else if (sort == 2) {// 按更新时间降序
            nativeSearchQueryBuilder.withSort(SortBuilders.fieldSort("updateTime").order(SortOrder.DESC));
        } else if (sort == 3) {// 按地址升序
            nativeSearchQueryBuilder.withSort(SortBuilders.fieldSort("address").order(SortOrder.ASC));
        } else {// 按相关度
            nativeSearchQueryBuilder.withSort(SortBuilders.scoreSort().order(SortOrder.DESC));
        }// 分页
        Pageable pageable = PageRequest.of(0, 2);
        nativeSearchQueryBuilder.withPageable(pageable);
        NativeSearchQuery searchQuery = nativeSearchQueryBuilder.build();
        System.out.println("DSL: " + searchQuery.getQuery().toString());
        SearchHits searchHits = elasticsearchRestTemplate.search(searchQuery, EsUser.class);if (searchHits.getTotalHits() <= 0) {
            page = new PageImpl<>(Collections.emptyList(), pageable, 0);
        }
        List esUserList = searchHits.stream().map(SearchHit::getContent).collect(Collectors.toList());
        page = new PageImpl<>(esUserList, pageable, searchHits.getTotalHits());
        System.out.println("总记录数:" + page.getTotalElements());
        System.out.println("当前页记录数:" + page.getNumberOfElements());
        System.out.println("每页记录数:" + page.getSize());
        System.out.println("总页数:" + page.getTotalPages());
        System.out.println("查询结果:" + page.getContent());
        System.out.println("当前页(从0开始计):" + page.getNumber());
        System.out.println("是否为首页:" + page.isFirst());
        System.out.println("是否为尾页:" + page.isLast());
    }/*=============== 自定义复杂查询(ElasticsearchRestTemplate)-结束 ===============*/
}

  • Spring Boot 教程合集(微信左下方阅读全文可直达)。
  • Spring Boot 教程合集示例代码:https://github.com/cxy35/spring-boot-samples
  • 本文示例代码:https://github.com/cxy35/spring-boot-samples/tree/master/spring-boot-dao/spring-boot-elasticsearch

相关文章:

  • java switch支持的数据类型_Java十四天零基础入门-Java关键字
  • flask框架_开发中Django和Flask框架的区别是什么?
  • 无法从命令行或调试器启动服务.必须首先_Emacs 调试秘籍之 GUD 调试器
  • googlenet网络结构_图像处理必读论文之五GoogLeNet-3
  • 编写tcp服务器发送hex格式_恶意程序编写之免杀基础
  • webgis从基础到开发实践_WebGIS开发进阶练手题(二)
  • idea 启动vue 一会自己停了_这 几个 IDEA,调试的骚操作,用了都说爽!
  • java 桌面应用程序_针对初学Java的小伙伴,入门时应该了解的Java基础知识
  • python面向对象思路_Python基础之面向对象的软件开发思路
  • python主要应用于云计算的哪些方面_python在云计算的应用领域
  • python小程序_如何使用 Python 开发微信小程序
  • 华为平板wps语音朗读_年轻人智慧生活:荣耀笔记本、平板V6,智慧屏X1等多款新品再升级...
  • python复制文件夹所有文件到另外目录_如何使用Python将文件的整个目录复制到现有目录中?...
  • python输入多个数字 找出只出现一个的数字_【算法14】找出数组中两个只出现一次的数字...
  • python随机抽人小程序_python抽人程序初试
  • 【391天】每日项目总结系列128(2018.03.03)
  • 【翻译】babel对TC39装饰器草案的实现
  • Android开源项目规范总结
  • AzureCon上微软宣布了哪些容器相关的重磅消息
  • Git 使用集
  • gulp 教程
  • javascript 哈希表
  • Kibana配置logstash,报表一体化
  • PHP 的 SAPI 是个什么东西
  • Redux 中间件分析
  • vue中实现单选
  • Vultr 教程目录
  • Xmanager 远程桌面 CentOS 7
  • 阿里云爬虫风险管理产品商业化,为云端流量保驾护航
  • 容器化应用: 在阿里云搭建多节点 Openshift 集群
  • 带你开发类似Pokemon Go的AR游戏
  • 微龛半导体获数千万Pre-A轮融资,投资方为国中创投 ...
  • ​ 无限可能性的探索:Amazon Lightsail轻量应用服务器引领数字化时代创新发展
  • ​iOS安全加固方法及实现
  • ​水经微图Web1.5.0版即将上线
  • #if和#ifdef区别
  • #宝哥教你#查看jquery绑定的事件函数
  • #考研#计算机文化知识1(局域网及网络互联)
  • (bean配置类的注解开发)学习Spring的第十三天
  • (html5)在移动端input输入搜索项后 输入法下面为什么不想百度那样出现前往? 而我的出现的是换行...
  • (分享)自己整理的一些简单awk实用语句
  • (三)Pytorch快速搭建卷积神经网络模型实现手写数字识别(代码+详细注解)
  • (三维重建学习)已有位姿放入colmap和3D Gaussian Splatting训练
  • (十二)springboot实战——SSE服务推送事件案例实现
  • (四)汇编语言——简单程序
  • (转)为C# Windows服务添加安装程序
  • .net 怎么循环得到数组里的值_关于js数组
  • .NET导入Excel数据
  • @Responsebody与@RequestBody
  • @TableLogic注解说明,以及对增删改查的影响
  • []新浪博客如何插入代码(其他博客应该也可以)
  • [17]JAVAEE-HTTP协议
  • [Android]使用Git将项目提交到GitHub
  • [Angular] 笔记 9:list/detail 页面以及@Output
  • [CISCN 2019华东南]Web11