SpringBoot项目集成ElasticSearch

news2025/3/6 17:29:40

1. 项目背景

处于失业找工作的阶段,随便写写吧~ 没啥背景,没啥意义,Java后端越来越卷了。第一学历不是本科,感觉真的是没有一点路可走。

如果有路过的小伙伴,如果身边还有坑位,不限第一学历的话,麻烦帮忙捞一下!非常感谢!

2. 项目环境

说下项目环境吧,尽量使用和自己SpringBoot匹配的版本,不然整合的过程中可能会有很多的坑~

image-20250305195052710

本文环境如下:

  • Springboot:2.7.12
  • JDK:1.8
  • elasticsearch:7.17.3

3. 下载安装

贴个ES下载链接,windows环境:https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-7.17.3-windows-x86_64.zip

然后还一个,相当于ES的可视化界面吧:https://github.com/mobz/elasticsearch-head

windows环境下很方便,这里就不多说了,简单说说需要修改的地方吧

3.1 elasticsearch配置

安装就是下载解压,官网下载速度很快,下载完直接解压就行。解压完大致如下:

image-20250305200144883

然后配置一下环境变量,ES_HOMEES_JAVA_HOME,注意改成自己的解压目录哈 ~

ES_HOME: D:\software\elasticsearch-7.17.3
ES_JAVA_HOME: D:\software\elasticsearch-7.17.3\jdk

image-20250305200437919

然后修改下/config下的elasticsearch.yml文件,最后添加一下内容,不然使用elasticsearch-head会报跨域的错误

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

好了然后直接前往/bin目录下直接双击elasticsearch.bat文件即可启动,默认9200端口。

启动后直接去浏览器输入http://127.0.0.1:9200/,访问到下面页面代表ES启动成功。

image-20250305200943691

3.2 elasticsearch-head配置

elasticsearch-head直接在github上拉代码就行。

这个依赖node环境,需要使用npm下载依赖。

进入到D:\software\elasticsearch-head,先通过npm i安装依赖,然后通过npm run start启动。

npm i
npm run start

启动访问http://127.0.0.1:9100/,点击了连接,可以看到以下界面 ,我的有点不纯了😂

image-20250305201448537

4. SpringBoot 集成Es

4.1 添加依赖

在Springboot中pom.xml中添加ES依赖

 		<!-- 引入ES依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
        </dependency>

4.2 项目使用

我这里直接用了笑小枫网站博客的内容,方便后面演示。

首先创建一个对象,用来定义存储数据的对象,对应ES中的mappings。对象如下:

package com.maple.es.bean;

import lombok.Data;
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;

import java.util.Date;
import java.util.List;

/**
 * @author 笑小枫 <https://www.xiaoxiaofeng.com/>
 * @date 2023/1/30
 */
@Data
@Document(indexName = "blog_title")
public class BlogDoc {

    @Id
    private Long id;

    @Field(type = FieldType.Long)
    private Long categoryId;

    @Field(type = FieldType.Text)
    private String title;

    @Field(type = FieldType.Text)
    private String content;

    @Field(type = FieldType.Keyword)
    private String description;

    @Field(type = FieldType.Keyword)
    private String keywords;

    @Field(type = FieldType.Keyword)
    private List<String> keywordList;

    private String status;

    @Field(type = FieldType.Integer)
    private Integer readNum;

    @Field(type = FieldType.Integer)
    private Integer collectNum;

    @Field(type = FieldType.Date)
    private Date createTime;
}

ES中对象类型这里只演示一下String、数值和时间。

image-20250305203028700

创建BlogRepository继承ElasticsearchRepository<BlogDoc, Long>接口。

其中BlogDoc是我们上面的对象。Long对应我们的主键,标 @Id的那个。

你可以在里面定义一些接口,也可以不定义。我这里只用到了继承的简单增删接口,查询使用ElasticsearchRestTemplate

package com.maple.es.repository;

import com.maple.es.bean.BlogDoc;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;

public interface BlogRepository extends ElasticsearchRepository<BlogDoc, Long> {

}

直接上BlogServiceImpl吧 ,详细的注释都放在代码里了,通义千问生成,大体懂。

核心部分看查询getList方法,涉及到分页了,简单定义了一个分页的对象

Page对象

package com.maple.es.bean;

import lombok.Data;

@Data
public class Page {

    private Integer page;

    private Integer size;

    private Long total;
}

简单的封装一下分页对象、请求参数和返回结果的对象

package com.maple.es.bean;

import lombok.Data;

import java.util.List;

@Data
public class BlogPageDoc {

    private Page page;

    private BlogDoc query;

    private List<BlogDoc> result;
}

【本文重点】Service实现类

package com.maple.es.service.impl;

import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.maple.es.bean.BlogDoc;
import com.maple.es.bean.BlogPageDoc;
import com.maple.es.bean.Page;
import com.maple.es.mapper.BlogMapper;
import com.maple.es.repository.BlogRepository;
import com.maple.es.service.BlogService;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.data.elasticsearch.core.SearchHits;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.data.elasticsearch.core.query.UpdateQuery;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;

@Service
public class BlogServiceImpl implements BlogService {

    @Autowired
    private BlogRepository blogRepository;

    @Autowired
    private ElasticsearchRestTemplate elasticsearchRestTemplate;

    @Autowired
    private BlogMapper blogTitleMapper;

    /**
     * 创建单条数据
     * <p>
     * 该方法负责将给定的BlogDoc对象保存到ES中
     *
     * @param blogBean 代表要创建的博客文章的文档对象
     */
    @Override
    public void createBlog(BlogDoc blogBean) {
        blogRepository.save(blogBean);
    }

    /**
     * 更新博客文档
     * <p>
     * 使用Elasticsearch的脚本更新功能来更新博客文档的标题和内容
     *
     * @param blogBean 包含要更新的博客信息的BlogDoc对象,包括博客的标题、内容和ID
     */
    @Override
    public void updateBlog(BlogDoc blogBean) {

        // 构建更新脚本,用于更新Elasticsearch文档的标题和内容
        // ctx._source 固定写法
        String script = "ctx._source.title = '" + blogBean.getTitle() + "';" +
                "ctx._source.content = '" + blogBean.getTitle() + "'";

        // 构建更新查询对象,指定要更新的文档ID和更新脚本
        UpdateQuery build = UpdateQuery.builder(String.valueOf(blogBean.getId())).withScript(script).build();

        // 指定要更新的索引名称
        IndexCoordinates indexCoordinates = IndexCoordinates.of("blog_title");

        // 执行更新操作,并获取更新响应
        elasticsearchRestTemplate.update(build, indexCoordinates);
    }

    /**
     * 初始化数据方法
     * <p>
     * 该方法用于从数据库中加载博客标题的相关信息,并处理关键词字符串,以便于后续的搜索或展示
     * 它首先从数据库中选择所有博客标题的信息,然后遍历这些信息,将每个博客标题的关键词字符串
     * 转换为关键词列表,最后将处理后的博客标题信息保存到博客仓库中
     */
    public void initData() {
        // 从数据库中选择所有博客标题的信息
        List<BlogDoc> list = blogTitleMapper.selectTitleList();

        // 遍历博客标题列表,处理每个博客标题的关键词
        for (BlogDoc blogTitleVo : list) {
            // 如果博客标题的关键词不为空,则将其转换为关键词列表
            if (blogTitleVo.getKeywords() != null) {
                blogTitleVo.setKeywordList(Arrays.asList(blogTitleVo.getKeywords().split(",")));
            }
        }

        // 将处理后的博客标题列表保存到博客仓库中
        blogRepository.saveAll(list);
    }

    /**
     * 删除指定ID的博客
     *
     * @param id 博客的唯一标识符
     */
    @Override
    public void deleteBlog(Long id) {
        blogRepository.deleteById(id);
    }

    @Override
    public BlogPageDoc getListBlogTitle(BlogPageDoc blogTitleVo) {
        return getList(blogTitleVo);
    }


    /**
     * 根据查询条件和分页信息获取博客列表
     *
     * @param blogPageDoc 包含查询条件和分页信息的博客页面文档
     * @return 返回填充了查询结果的博客页面文档
     */
    private BlogPageDoc getList(BlogPageDoc blogPageDoc) {

        // 获取查询条件
        BlogDoc blogDoc = blogPageDoc.getQuery();
        // 获取分页信息
        Page page = blogPageDoc.getPage();

        // 创建高亮构建器
        HighlightBuilder highlightBuilder = new HighlightBuilder();

        // 设置需要高亮的字段
        highlightBuilder.field("title");
        highlightBuilder.field("description");
        // 设置高亮显示的前缀和后缀
        highlightBuilder.preTags("<span style=\"font-color :red\">");
        highlightBuilder.postTags("</span>");

        // 创建布尔查询构建器
        BoolQueryBuilder queryBuilders = QueryBuilders.boolQuery();
        // 如果标题关键字不为空,则必须匹配keywordList中的关键字
        if (StringUtils.isNotBlank(blogDoc.getTitle())) {
            // list中有一个匹配成功,就代表成功,虽然指定了keywordList的类型,但是在es中还是text(不知道是不是哪个地方bug了,暂未验证),所以指定keywordList.keyword
            queryBuilders.must(QueryBuilders.termsQuery("keywordList.keyword", blogDoc.getKeywordList()));
        }
        // 如果类别ID不为空,则必须匹配类别ID
        if (blogDoc.getCategoryId() != null) {
            queryBuilders.must(QueryBuilders.termQuery("categoryId", blogDoc.getCategoryId()));
        }
        // 如果blogDoc的阅读数量不为空,则在查询中添加阅读数量的范围条件
        if (blogDoc.getReadNum() != null) {
            // 构建一个范围查询,筛选出阅读数量大于当前博客阅读数量的文档
            queryBuilders.must(QueryBuilders.rangeQuery("readNum").gt(blogDoc.getReadNum()));
        }
        // 如果标题不为空,则应匹配标题、描述或内容中的标题
        if (StringUtils.isNotBlank(blogDoc.getTitle())) {
            queryBuilders.should(QueryBuilders.matchQuery("title", blogDoc.getTitle()));
            queryBuilders.should(QueryBuilders.matchQuery("description", blogDoc.getTitle()));
            queryBuilders.should(QueryBuilders.matchQuery("content", blogDoc.getTitle()));
            // 设置至少应匹配的条件数
            queryBuilders.minimumShouldMatch(1);
        }
        // 执行搜索并获取结果
        SearchHits<BlogDoc> searchHits = elasticsearchRestTemplate.search(new NativeSearchQueryBuilder()
                        .withQuery(queryBuilders) // 设置查询条件
                        .withSorts(SortBuilders.fieldSort("readNum").order(SortOrder.DESC)) // 设置排序条件
                        .withHighlightBuilder(highlightBuilder)  // 设置高亮构建器
                        .withPageable(PageRequest.of(page.getPage() - 1, page.getSize()))  // 设置分页信息
                        .build(),
                BlogDoc.class);
        // 创建结果列表
        List<BlogDoc> result = new ArrayList<>();
        // 设置总记录数
        page.setTotal(searchHits.getTotalHits());
        // 遍历搜索结果并处理高亮显示
        searchHits.getSearchHits().forEach(hits -> {
            BlogDoc blogTitle = hits.getContent();
            Map<String, List<String>> highlightFields = hits.getHighlightFields();

            // 如果标题的高亮字段不为空,则替换标题
            if (!CollectionUtils.isEmpty(highlightFields.get("title"))) {
                blogTitle.setTitle(hits.getHighlightFields().get("title").get(0));
            }
            // 如果描述的高亮字段不为空,则替换描述
            if (!CollectionUtils.isEmpty(highlightFields.get("description"))) {
                blogTitle.setDescription(hits.getHighlightFields().get("description").get(0));
            }
            // 将处理后的结果添加到列表中
            result.add(blogTitle);
        });
        // 设置查询结果
        blogPageDoc.setResult(result);
        // 返回填充了查询结果的博客页面文档
        return blogPageDoc;
    }
}

可以在elasticsearch-head看到对应的数据,这里就不一步一步演示了

image-20250305212410963

5.结语

文中只放了一些核心的代码

一些调用数据的代码和Controller的代码都没贴,非核心

需要的小伙伴可以前往github拉源码看一下。

本文源码:https://github.com/hack-feng/maple-product/tree/main/maple-es

🐾我是笑小枫,全网皆可搜的【笑小枫】

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2310626.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

DeepSeek大模型深度解析:架构、技术与应用全景

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站。https://www.captainbed.cn/north 文章目录 一、大模型时代与DeepSeek的定位1.1 大模型发展历程回顾大模型发展历程时间轴&#xff08;20…

Dubbo+Zookeeper

Apache ZooKeeper 通过当前页面下载Zookeeper 在这里启动zookeeper 可以根据这个页面简单学习一下&#xff0c;但是没有集成mysql&#xff0c;也会出现一些报错&#xff0c;且在这之后我们要使用的管理页面是vue的dubbo-admin dubbo学习三&#xff1a;springboot整合dubbozo…

从厨电模范到数字先锋,看永洪科技如何助力方太集团开启数字新征程

在数字化洪流席卷全球的宏大背景下&#xff0c;企业转型升级的紧迫性与重要性日益凸显&#xff0c;成为驱动行业进步的关键引擎。在这一波澜壮阔的转型浪潮中&#xff0c;方太集团——厨电领域的璀璨明珠&#xff0c;以其前瞻性的战略视野和不懈的创新精神&#xff0c;携手数据…

前端基础之组件自定义事件

我们可以通过使用给组件绑定事件&#xff0c;当组件触发该事件时&#xff0c;就能进行值得返回 我们可以使用v-on属性来给子组件绑定自定义事件&#xff0c;此时该事件就会存在vc中&#xff0c;然后通过this.$emit来触发绑定的事件&#xff0c; 这样就能实现不需要app.vue来给子…

基于DeepSeek(本地部署)和RAGFlow构建个人知识库

总结自视频&#xff08;很强的小姐姐视频&#xff0c;讲解清晰明了&#xff09;&#xff1a;【知识科普】【纯本地化搭建】【不本地也行】DeepSeek RAGFlow 构建个人知识库_哔哩哔哩_bilibili 1. 背景 deepseek官方网页版也虽然很强&#xff0c;能够满足绝大部分需求&#xf…

学习工具的一天之(burp)

第一呢一定是先下载 【Java环境】&#xff1a;Java Downloads | Oracle 下来是burp的下载 Download Burp Suite Community Edition - PortSwigger 【下载方法二】关注的一个博主 【BurpSuite 安装激活使用详细上手教程 web安全测试工具】https://www.bilibili.com/video/BV…

2025-03-05 学习记录--C/C++-PTA 习题5-8 空心的数字金字塔

合抱之木&#xff0c;生于毫末&#xff1b;九层之台&#xff0c;起于累土&#xff1b;千里之行&#xff0c;始于足下。&#x1f4aa;&#x1f3fb; 一、题目描述 ⭐️ 二、解题步骤 ⭐️ 下面以n5举例&#xff1a;&#x1f98b; 第1步 &#x1f380;、外层循环&#xff08;从1到…

vue+neo4j 四大名著知识图谱问答系统

编号: D039 视频 vueneo4j四大名著知识图谱问答系统 技术架构 vuedjangoneo4jmysql技术实现 功能模块图 问答&#xff1a;基于知识图谱检索、支持图多跳、显示推理路径 姜维的师傅的主公的臣是谁&#xff1a; 马谡 知识图谱&#xff1a;四大名著总共4个图谱 红楼梦图谱 …

【智能体架构:Agent】LangChain智能体类型ReAct、Self-ASK的区别

1. 什么是智能体 将大语言模型作为一个推理引擎。给定一个任务&#xff0c; 智能体自动生成完成任务所需步骤&#xff0c; 执行相应动作&#xff08;例如选择并调用工具&#xff09;&#xff0c; 直到任务完成。 2. 先定义工具&#xff1a;Tools 可以是一个函数或三方 API也…

基于eRDMA实测DeepSeek开源的3FS

DeepSeek昨天开源了3FS分布式文件系统, 通过180个存储节点提供了 6.6TiB/s的存储性能, 全面支持大模型的训练和推理的KVCache转存以及向量数据库等能力, 每个客户端节点支持40GB/s峰值吞吐用于KVCache查找. 发布后, 我们在阿里云ECS上进行了快速的复现, 并进行了性能测试, ECS…

Vue的简单入门 三

目录 侦听器 watch 注意 表单输入绑定 v-model v-model修饰符​编辑 lazy number Trim 模板引用 组件组成 组件引用三步走 组件的嵌套关系 header Main Aside Aritice Item App.vue组件引入三个子组件 组件的注册方式 全局注册组件的方法 (1) Vue 2 语…

指纹细节提取(Matlab实现)

指纹细节提取概述指纹作为人体生物特征识别领域中应用最为广泛的特征之一&#xff0c;具有独特性、稳定性和便利性。指纹细节特征对于指纹识别的准确性和可靠性起着关键作用。指纹细节提取&#xff0c;即从指纹图像中精确地提取出能够表征指纹唯一性的关键特征点&#xff0c;是…

STM32——串口通信 UART

一、基础配置 Universal Asynchronous Receiver Transmitter 异步&#xff0c;串行&#xff0c;全双工 TTL电平 &#xff1a;高电平1 低电平0 帧格式&#xff1a; 起始位1bit 数据位8bit 校验位1bit 终止位1bit NVIC Settings一栏使能接受中断。 之前有设置LCD&#xff0c;…

PHP fastadmin 学习

安装php环境安装mysql插件 修改 php.ini下载 phpstudy、fastadmin 错误 安装FastAdmin could not find driver 参考链接 安装插件 创建1.php <? phpinfo(); ?>运行 http://127.0.0.1/1.php 查看 POD 页面访问404 伪静态 Apache <IfModule mod_rewrite.c> O…

Autojs无线连接vscode方法

1.获得电脑的IP 在电脑的CMD界面输入 ipconfig 然后找到ipv4的那一行&#xff0c;后面的即是你的电脑IP地址 2.打开vscode的autojs服务 安装autojs插件 在vscode界面按下ctrlshiftp 输入autojs 找到 点击 之后打开手机上的autojs 之后输入刚刚电脑上的地址 可以看到vsc…

天津大学02-深度解读DeepSeek:部署、使用、安全【文末附下载链接】

大模型风险与不当用例——价值观错位 大模型与人类价值观、期望之间的不一致而导致的安全问题&#xff0c;包含&#xff1a;• 社会偏见&#xff08;Social Bias&#xff09;LLM在生成文本时强化对特定社会群体的刻板印象&#xff0c;例如将穆斯林与恐怖主义关联&#xff0c;或…

SPI驱动(三) -- SPI设备树处理过程

文章目录 参考资料&#xff1a;一、SPI设备树节点构成二、SPI设备树示例2.1 SPI控制器节点属性2.2 SPI设备节点属性 三、SPI设备树处理过程四、总结 参考资料&#xff1a; 内核头文件&#xff1a;include\linux\spi\spi.h内核文档&#xff1a;Documentation\devicetree\bindin…

MARL零样本协调之Fictitious Co-Play学习笔记

下列引用来自知乎作者Algernon 知乎link FCP作为ZSC领域两阶段训练方法的开创者 论文《Collaborating with Humans without Human Data》来自 NeurIPS 2021。这篇论文提出 Fictitious Co-Play (FCP) 来解决 ZSC 问题。论文认为&#xff0c;ZSC 的第一个重要问题是对称性&#x…

idea中的查看git历史记录,不显示详细信息

一、正常情况显示 1、idea中git查看history正常显示如下图&#xff1a; 二、非正常情况下显示 1、idea中git查看history&#xff0c;现在不显示提交的历史文件详细信息&#xff0c;如下图&#xff1a; 三、解决方式 1、找到如下窗口中画红色框的黑色线条&#xff0c;鼠标放在…

Redis——快速入门

目录 Redis简介 安装配置(Windows) GUI工具RedisInsight的使用 十大数据类型&#xff08;5基本5高级&#xff09; 字符串String 列表List 集合Set(S) 有序集合SortedSet(Z) 哈希Hash(H) 发布订阅模式 消息队列Stream(X) 地理空间Geospatial(GEO) HyperLogLog(PF) …