news 2026/4/23 22:23:15

SpringBoot 整合 ElasticSearch,给搜索插上“光速翅膀”

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
SpringBoot 整合 ElasticSearch,给搜索插上“光速翅膀”

大家好,我是小悟。

一、ElasticSearch 是什么?

你有一个超级健忘的朋友(比如金鱼记忆的那种),但他却能在0.0001秒内从100万本书里找到你想要的句子。这就是 ElasticSearch(简称 ES)!

ES 的“人格特征”:

  • 速度狂魔:搜索速度比咖啡因过量的程序员找Bug还快
  • 文档收藏家:什么JSON、日志都能存
  • 超级侦探:模糊搜索、精确搜索、拼音搜索样样精通
  • 大象胃口:数据量再大也不怕(毕竟名字里就有“elastic”弹性)
  • 马戏团团长:天生分布式,节点之间跳来跳去从不出错

二、整合大冒险:SpringBoot 与 ES 的“相亲大会”

第 1 步:先来个“相亲介绍人”(Maven 依赖)

<!-- pom.xml 里加入这些“红娘” --> <dependencies> <!-- SpringBoot 给 ES 的专属“情书” --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-elasticsearch</artifactId> </dependency> <!-- 防止程序说“我不会JSON” --> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> </dependency> </dependencies>

第 2 步:配置“约会地点”(application.yml)

# application.yml spring: elasticsearch: uris: http://localhost:9200 # ES 的“家庭地址” username: elastic # 用户名(默认是这个) password: your_password # 密码(安装时设置的) # 可选:让日志“多说点话”,方便调试 data: elasticsearch: repositories: enabled: true # 给 ES 客户端一点“咖啡因”,让它更精神 elasticsearch: connection-timeout: 5000 # 连接超时(毫秒) socket-timeout: 30000 # socket超时

第 3 步:创建“相亲对象”(实体类)

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; @Data @Document(indexName = "book_index") // 告诉ES:“这是我家的书架名字” public class Book { @Id // 相当于书的“身份证号” private String id; @Field(type = FieldType.Text, analyzer = "ik_max_word") // 用中文分词器 private String title; // 书名 @Field(type = FieldType.Text, analyzer = "ik_smart") private String author; // 作者 @Field(type = FieldType.Double) private Double price; // 价格 @Field(type = FieldType.Date) private Date publishDate; // 出版日期 @Field(type = FieldType.Keyword) // 关键词,不分词 private String category; // 分类 @Field(type = FieldType.Nested) // 嵌套对象 private List<Tag> tags; @Data public static class Tag { @Field(type = FieldType.Keyword) private String name; @Field(type = FieldType.Integer) private Integer priority; } }

第 4 步:找个“媒婆”(Repository 接口)

import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.elasticsearch.annotations.Query; import org.springframework.data.elasticsearch.repository.ElasticsearchRepository; import org.springframework.stereotype.Repository; import java.util.List; @Repository public interface BookRepository extends ElasticsearchRepository<Book, String> { // 方法名就是查询!Spring Data 的“魔法” // 1. 按作者精确查找(比找失散多年的兄弟还准) List<Book> findByAuthor(String author); // 2. 按标题模糊查找(支持分词) List<Book> findByTitleContaining(String keyword); // 3. 价格区间查找(找买得起的书) List<Book> findByPriceBetween(Double minPrice, Double maxPrice); // 4. 多条件查询(作者+分类) List<Book> findByAuthorAndCategory(String author, String category); // 5. 自定义查询(展示真正的技术) @Query("{\"bool\": {\"must\": [{\"match\": {\"title\": \"?0\"}}]}}") Page<Book> customSearch(String keyword, Pageable pageable); // 6. 统计某个作者有多少书 Long countByAuthor(String author); }

第 5 步:写个“恋爱导师”(Service 层)

import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import java.util.List; import java.util.Optional; @Service public class BookService { @Autowired private BookRepository bookRepository; /** * 添加/更新一本书 * 如果书有id,就是更新;没有id,就是新增 */ public Book saveBook(Book book) { return bookRepository.save(book); } /** * 批量添加(ES最喜欢批量操作了,效率高) */ public void saveAllBooks(List<Book> books) { bookRepository.saveAll(books); } /** * 按ID查找(速度飞快) */ public Optional<Book> findById(String id) { return bookRepository.findById(id); } /** * 复杂搜索:按标题和作者搜索 */ public List<Book> searchBooks(String title, String author) { // 这里可以写更复杂的逻辑 if (title != null && author != null) { return bookRepository.findByTitleContainingAndAuthor(title, author); } else if (title != null) { return bookRepository.findByTitleContaining(title); } else { return bookRepository.findByAuthor(author); } } /** * 删除一本书(谨慎操作!) */ public void deleteBook(String id) { bookRepository.deleteById(id); } /** * 分页查询(大数据量的好朋友) */ public Page<Book> findAllBooks(Pageable pageable) { return bookRepository.findAll(pageable); } /** * 高级搜索:使用QueryBuilder */ public List<Book> advancedSearch(String keyword, Double minPrice, Double maxPrice) { // 使用NativeSearchQueryBuilder构建复杂查询 // 这里先省略,下面会有详细示例 return null; } }

第 6 步:高级搜索的“秘密武器”

import org.elasticsearch.index.query.BoolQueryBuilder; import org.elasticsearch.index.query.QueryBuilders; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate; import org.springframework.data.elasticsearch.core.SearchHits; import org.springframework.data.elasticsearch.core.query.NativeSearchQuery; import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder; import org.springframework.stereotype.Service; import java.util.List; import java.util.stream.Collectors; @Service public class AdvancedSearchService { @Autowired private ElasticsearchRestTemplate elasticsearchRestTemplate; /** * 多条件组合搜索(布尔查询) */ public List<Book> multiConditionSearch(String keyword, Double minPrice, Double maxPrice, String category) { // 1. 创建布尔查询构建器(相当于SQL的WHERE) BoolQueryBuilder boolQuery = QueryBuilders.boolQuery(); // 2. 添加必须条件(must = AND) if (keyword != null && !keyword.trim().isEmpty()) { boolQuery.must(QueryBuilders.multiMatchQuery(keyword, "title", "author") .analyzer("ik_max_word")); } // 3. 添加价格范围(range查询) if (minPrice != null || maxPrice != null) { var rangeQuery = QueryBuilders.rangeQuery("price"); if (minPrice != null) { rangeQuery.gte(minPrice); } if (maxPrice != null) { rangeQuery.lte(maxPrice); } boolQuery.must(rangeQuery); } // 4. 添加分类过滤(filter不计算分数,更快) if (category != null && !category.trim().isEmpty()) { boolQuery.filter(QueryBuilders.termQuery("category", category)); } // 5. 构建查询 NativeSearchQuery searchQuery = new NativeSearchQueryBuilder() .withQuery(boolQuery) .withSorts(org.springframework.data.elasticsearch.core.query.SortBuilders .fieldSort("price").order(org.springframework.data.domain.Sort.Direction.ASC)) .withPageable(org.springframework.data.domain.PageRequest.of(0, 10)) .build(); // 6. 执行查询 SearchHits<Book> searchHits = elasticsearchRestTemplate.search(searchQuery, Book.class); // 7. 转换结果 return searchHits.getSearchHits().stream() .map(hit -> hit.getContent()) .collect(Collectors.toList()); } /** * 聚合查询:统计每个分类有多少本书 */ public Map<String, Long> categoryStatistics() { NativeSearchQuery query = new NativeSearchQueryBuilder() .addAggregation(org.springframework.data.elasticsearch.core.query.aggregation.AggregationBuilders .terms("category_agg").field("category.keyword")) .build(); SearchHits<Book> searchHits = elasticsearchRestTemplate.search(query, Book.class); // 处理聚合结果(这里简化了,实际需要解析Aggregations) return new HashMap<>(); } }

第 7 步:REST API 控制器(对外接口)

import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import java.util.List; import java.util.Optional; @RestController @RequestMapping("/api/books") public class BookController { @Autowired private BookService bookService; @Autowired private AdvancedSearchService advancedSearchService; /** * 创建新书 */ @PostMapping public ResponseEntity<Book> createBook(@RequestBody Book book) { Book savedBook = bookService.saveBook(book); return ResponseEntity.ok(savedBook); } /** * 批量导入(适合初始化数据) */ @PostMapping("/batch") public ResponseEntity<String> batchImport(@RequestBody List<Book> books) { bookService.saveAllBooks(books); return ResponseEntity.ok("成功导入 " + books.size() + " 本书"); } /** * 搜索书籍(简单版) */ @GetMapping("/search") public ResponseEntity<List<Book>> searchBooks( @RequestParam(required = false) String title, @RequestParam(required = false) String author) { List<Book> books = bookService.searchBooks(title, author); return ResponseEntity.ok(books); } /** * 高级搜索(多条件) */ @GetMapping("/advanced-search") public ResponseEntity<List<Book>> advancedSearch( @RequestParam(required = false) String keyword, @RequestParam(required = false) Double minPrice, @RequestParam(required = false) Double maxPrice, @RequestParam(required = false) String category) { List<Book> books = advancedSearchService .multiConditionSearch(keyword, minPrice, maxPrice, category); return ResponseEntity.ok(books); } /** * 分页查询 */ @GetMapping("/page") public ResponseEntity<Page<Book>> getBooksByPage( @RequestParam(defaultValue = "0") int page, @RequestParam(defaultValue = "10") int size) { Page<Book> books = bookService.findAllBooks(PageRequest.of(page, size)); return ResponseEntity.ok(books); } }

第 8 步:配置类(锦上添花)

import org.elasticsearch.client.RestHighLevelClient; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.elasticsearch.client.ClientConfiguration; import org.springframework.data.elasticsearch.client.RestClients; import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate; import org.springframework.data.elasticsearch.repository.config.EnableElasticsearchRepositories; @Configuration @EnableElasticsearchRepositories(basePackages = "com.yourpackage.repository") public class ElasticsearchConfig { @Bean public RestHighLevelClient elasticsearchClient() { ClientConfiguration clientConfiguration = ClientConfiguration.builder() .connectedTo("localhost:9200") .withBasicAuth("elastic", "your_password") .withConnectTimeout(5000) .withSocketTimeout(30000) .build(); return RestClients.create(clientConfiguration).rest(); } @Bean public ElasticsearchRestTemplate elasticsearchRestTemplate() { return new ElasticsearchRestTemplate(elasticsearchClient()); } }

三、测试一下我们的“杰作”

测试数据(JSON格式)

POST /api/books/batch [ { "title": "SpringBoot从入门到放弃", "author": "程序猿老张", "price": 68.5, "category": "技术", "publishDate": "2023-01-01", "tags": [ {"name": "Java", "priority": 1}, {"name": "后端", "priority": 2} ] }, { "title": "ElasticSearch实战指南", "author": "搜索达人李", "price": 89.0, "category": "技术", "publishDate": "2023-02-15", "tags": [ {"name": "搜索", "priority": 1}, {"name": "大数据", "priority": 2} ] } ]

搜索示例

GET /api/books/search?title=SpringBoot&author=程序猿老张 GET /api/books/advanced-search?keyword=实战&minPrice=50&maxPrice=100

四、遇到的“坑”和解决方案

  1. 连接问题:确保ES服务已启动,检查端口9200

    curl http://localhost:9200
  2. 版本兼容:SpringBoot版本和ES版本要匹配

    • SpringBoot 2.7.x → ES 7.17.x
    • SpringBoot 3.x → ES 8.x
  3. 中文分词:安装IK分词器

    # 进入ES容器/安装目录的plugins文件夹 ./bin/elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.17.3/elasticsearch-analysis-ik-7.17.3.zip
  4. 内存不足:调整JVM参数

    # config/jvm.options -Xms1g -Xmx1g

五、总结:为什么选择这对“黄金搭档”?

SpringBoot + ES = 天作之合

优点大放送:

  1. 开发速度:SpringBoot的自动配置 + ES的简单API = 生产力翻倍
  2. 性能表现:ES的倒排索引 + 分布式架构 = 搜索如飞
  3. 扩展性:微服务架构中,ES可作为独立的搜索服务
  4. 生态完善:Spring Data Elasticsearch 封装了大多数常用操作
  5. 实时性:近实时搜索,数据一秒内可查

适用场景(ES大显身手的时候)

  1. 电商网站:商品搜索、筛选、排序
  2. 日志分析:ELK栈中的核心组件
  3. 内容平台:文章、新闻的全文检索
  4. 监控系统:实时数据分析
  5. 推荐系统:用户行为分析+相似度搜索

最后

  • 不要滥用:简单的CRUD用MySQL,复杂搜索再用ES
  • 数据同步:考虑使用Logstash或自定义同步机制
  • 索引设计:合理的mapping设计是性能的关键
  • 监控告警:用Kibana监控ES集群健康状态

SpringBoot整合ElasticSearch,就像给程序装上了“谷歌大脑”——存得多、找得快、查得准。虽然配置过程像在组装乐高,偶尔会找不到零件(版本兼容),但一旦搭建完成,你就能享受到“秒级搜索”的快感。

谢谢你看我的文章,既然看到这里了,如果觉得不错,随手点个赞、转发、在看三连吧,感谢感谢。那我们,下次再见。

您的一键三连,是我更新的最大动力,谢谢

山水有相逢,来日皆可期,谢谢阅读,我们再会

我手中的金箍棒,上能通天,下能探海

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/23 14:29:48

GPT-OSS-20B性能对比:低延迟与高效推理全解析

GPT-OSS-20B性能解析&#xff1a;如何在16GB内存中实现接近GPT-4的推理体验 你有没有试过在自己的笔记本上跑一个“类GPT-4”级别的语言模型&#xff1f;不是那种简化到只剩皮囊的小模型&#xff0c;而是真正具备复杂推理、长文本理解和结构化输出能力的大模型。听起来像天方夜…

作者头像 李华
网站建设 2026/4/23 12:10:50

GitHub必备收藏:这个项目汇集了所有你需要的LLM应用实例

在AI应用开发的浪潮中&#xff0c;你是否还在为如何构建实用的LLM应用而困惑&#xff1f;是否想要学习RAG、AI代理、多模态应用的最佳实践&#xff1f;Shubham Saboo的Awesome LLM Apps项目为所有AI开发者提供了一个完整的学习和参考宝库&#xff0c;从基础教程到高级应用&…

作者头像 李华
网站建设 2026/4/23 10:44:12

Foundation 网格 - 小型设备

Foundation 网格系统在小型设备&#xff08;Small Devices&#xff09;上的行为 Foundation&#xff08;特别是 XY Grid&#xff09;采用 移动优先&#xff08;Mobile-First&#xff09; 设计原则。这意味着&#xff1a; 小型设备&#xff08;small breakpoint&#xff09; 是…

作者头像 李华
网站建设 2026/4/23 13:28:53

Foundation 网格实例

Foundation XY Grid 常见实例 以下是几个实用 Foundation XY Grid 的完整代码实例&#xff0c;涵盖响应式布局、块状网格、偏移、对齐等常见场景。你可以直接复制到 HTML 文件中测试&#xff08;需引入 Foundation CSS/JS&#xff09;。 1. 基本响应式三列布局&#xff08;经…

作者头像 李华
网站建设 2026/4/23 12:10:25

kotaemon社区支持全攻略:从安装到问答

kotaemon社区支持全攻略&#xff1a;从安装到问答 在企业级智能对话系统开发中&#xff0c;一个常见的痛点是&#xff1a;如何让AI既具备强大的语言生成能力&#xff0c;又能准确引用内部知识库中的信息&#xff1f;许多团队尝试过简单的“文档上传大模型”方案&#xff0c;但…

作者头像 李华
网站建设 2026/4/23 12:11:57

原始数据—>张量转换后会丢失原始数据吗

学习李沐香蕉目标检测时疑问原始数据 → 张量的转换链路&#xff08;全程可回溯&#xff09;:1. 图像原始数据的转换链路硬盘上的.png文件&#xff08;原始数据&#xff09;↓ 由torchvision.io.read_image读取单个图像张量&#xff08;uint8&#xff0c;[C,H,W]&#xff09;→…

作者头像 李华