news 2026/4/3 2:58:29

手把手教程:SpringBoot整合Elasticsearch实现商品搜索

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
手把手教程:SpringBoot整合Elasticsearch实现商品搜索

手把手实战:用 Spring Boot 搭建高性能商品搜索引擎

你有没有遇到过这样的场景?用户在电商网站搜索“华为手机”,系统卡顿半秒才返回结果,翻到第二页又慢了一拍——这种体验,在高并发、大数据量的今天已经无法接受。而背后的原因,往往就是还在用 MySQL 的LIKE做模糊查询

别急,今天我们不讲理论堆砌,也不复制官方文档,而是带你从零开始,亲手搭建一个基于 Spring Boot 和 Elasticsearch 的商品搜索系统。整个过程就像写一篇开发日记:你会看到我踩过的坑、调过的参数、优化过的查询逻辑,以及最终上线后性能提升 10 倍的真实效果。

准备好了吗?我们直接开干。


为什么不能只靠数据库做搜索?

先说个真实案例。某电商平台早期所有数据都存在 MySQL 里,商品表不到 50 万条时还好,但一旦超过百万,哪怕加了索引,SELECT * FROM product WHERE title LIKE '%手机%'这种语句也常常耗时800ms~2s

更麻烦的是:
- 中文分词难处理,“智能手机”搜不到“智能 手机”;
- 多条件组合(比如分类+价格区间+品牌)会让 SQL 越写越复杂;
- 分页深了会变慢,LIMIT 10000, 10直接全表扫描。

这时候,你就需要一个真正的搜索引擎——Elasticsearch

它不是数据库替代品,而是专门为“查得快”设计的工具。它的核心能力是:
- 支持近实时全文检索(NRT),新数据 1 秒内可搜;
- 内置倒排索引机制,关键词查找效率极高;
- 提供丰富的 DSL 查询语言,轻松实现布尔、范围、模糊、聚合等高级功能;
- 分布式架构天然支持横向扩展。

简单来说:MySQL 负责存,ES 负责搜。两者配合,才是现代系统的标准解法。


技术选型:Spring Data Elasticsearch 到底香在哪?

Spring Boot 生态中整合 ES 有好几种方式:原生 REST Client、Jest、OpenSearch SDK……但我们选择最主流也最省心的一种——Spring Data Elasticsearch

它到底解决了什么问题?

传统做法使用 Spring Data Elasticsearch
手动拼 JSON 查询 DSL方法名即查询逻辑,如findByTitleContaining()
自己管理连接和异常自动配置 RestClient,无缝集成 Spring 生命周期
实体与文档映射靠注释记忆注解驱动,@Document,@Field清晰直观
分页要自己算 offset直接传Pageable,自动处理分页

一句话总结:它把复杂的 ES 操作,变成了像操作数据库一样的 CRUD 编程体验

而且从 Spring Data Elasticsearch 4.x 开始,默认使用REST High Level Client,兼容性更好,支持 HTTPS 和认证,稳定性也更强。


动手实战:六步完成整合

我们来一步步搭建这个搜索系统。假设你现在有一个全新的 Spring Boot 项目,接下来的操作可以直接复用。

第一步:引入依赖(别搞错版本!)

<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- 关键依赖 --> <dependency> <groupId>org.springframework.data</groupId> <artifactId>spring-data-elasticsearch</artifactId> </dependency> <!-- Lombok 简化代码(可选) --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <scope>provided</scope> </dependency> </dependencies>

⚠️ 版本对齐很重要!
- Spring Boot 2.7.x → Spring Data Elasticsearch 4.4.x
- Spring Boot 3.x → 必须用 5.x,并注意包路径变化(Jakarta EE)

如果你用的是 Docker 启动的 ES,确保版本匹配。本文以Elasticsearch 8.11.0为例。


第二步:配置连接信息

spring: data: elasticsearch: client: reactive: endpoints: localhost:9200 # ES 地址 repositories: enabled: true # 启用仓库模式

就这么一行地址,Spring 就会自动创建RestClient并连接集群。如果启用了安全认证(比如 X-Pack),还需要额外配置用户名密码或证书,这里暂不展开。


第三步:定义商品实体类

这是最关键的一步——如何将 Java 对象映射成 ES 文档。

@Document(indexName = "product") @Data @NoArgsConstructor @AllArgsConstructor public class Product { @Id private String id; @Field(type = FieldType.Text, analyzer = "ik_max_word", searchAnalyzer = "ik_smart") private String title; @Field(type = FieldType.Keyword) private String category; @Field(type = FieldType.Double) private Double price; @Field(type = FieldType.Integer) private Integer stock; @Field(type = FieldType.Date) private Date createTime; @Field(type = FieldType.Boolean) private Boolean onSale; }

几个关键点解释一下:

🔹@Document(indexName = "product")

告诉框架这个类对应 ES 中的product索引。如果没有,启动时可以自动创建(需开启自动索引)。

🔹 中文分词怎么破?

默认分词器对中文是按单字切分的,比如“华为手机”会被切成“华”、“为”、“手”、“机”。显然不行!

解决方案:安装IK Analyzer 插件

# 在 Elasticsearch 安装目录下执行 ./bin/elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v8.11.0/elasticsearch-analysis-ik-8.11.0.zip

然后重启 ES。之后就可以在字段上指定分词器:

  • analyzer = "ik_max_word":索引时尽可能多切词,提高召回率;
  • searchAnalyzer = "ik_smart":搜索时智能少切词,提高准确率。

这样,“华为P60”能被正确识别为“华为”、“P60”,而不是一堆单字。

🔹KeywordvsText的区别?
  • Text:用于全文检索,会分词,适合标题、描述;
  • Keyword:不分词,完整匹配,适合分类、品牌、状态等精确筛选字段。

记住了:你要模糊搜的字段用Text,要精准查的用Keyword


第四步:编写 Repository 接口

Spring Data 的精髓就在这里——方法名即查询语义

public interface ProductRepository extends ElasticsearchRepository<Product, String> { // 根据类别和价格区间查询 List<Product> findByCategoryAndPriceBetween(String category, Double minPrice, Double maxPrice); // 标题包含关键词(支持分词) List<Product> findByTitleContaining(String keyword); // 上架中 + 标题匹配,带分页 Page<Product> findByOnSaleTrueAndTitleContaining(String keyword, Pageable pageable); }

你看,完全不用写 SQL 或 DSL,只要命名规范,框架就会自动生成对应的 ES 查询。

✅ 提示:这些方法底层生成的是match查询,属于query context;如果是onSale=true这种过滤条件,建议放在filter context更高效(后面会讲优化技巧)。


第五步:Service 层实现动态查询

实际业务中,用户的筛选条件往往是可选的。比如搜索页有多个输入框,有的填了,有的没填。这时候就不能依赖固定的方法名了。

我们需要手动构建查询条件。Spring Data 提供了CriteriaQuery工具类。

@Service public class ProductService { @Autowired private ProductRepository productRepository; public Page<Product> searchProducts(String keyword, String category, Double minPrice, Double maxPrice, int page, int size) { Pageable pageable = PageRequest.of(page, size, Sort.by("price").asc()); if (keyword != null && !keyword.trim().isEmpty()) { // 有关键词时优先走复合查询 return productRepository.findByOnSaleTrueAndTitleContaining(keyword, pageable); } // 否则走动态条件组合 Criteria criteria = new Criteria(); criteria.and(Criteria.where("onSale").is(true)); if (category != null && !category.isEmpty()) { criteria.and(Criteria.where("category").is(category)); } if (minPrice != null) { criteria.and(Criteria.where("price").greaterThanEqual(minPrice)); } if (maxPrice != null) { criteria.and(Criteria.where("price").lessThanEqual(maxPrice)); } Query query = new CriteriaQuery(criteria).setPageable(pageable); return productRepository.search(query); } public Product saveProduct(Product product) { return productRepository.save(product); } public void deleteById(String id) { productRepository.deleteById(id); } }

这段代码有几个亮点:

  • 使用CriteriaQuery构建动态条件,避免大量 if-else 拼接;
  • save()是 upsert 行为:ID 存在则更新,不存在则插入;
  • 分页通过Pageable控制,防止内存溢出。

第六步:暴露 HTTP 接口

最后一步,让前端能调用。

@RestController @RequestMapping("/api/products") public class ProductController { @Autowired private ProductService productService; @GetMapping public ResponseEntity<Page<Product>> search( @RequestParam(required = false) String keyword, @RequestParam(required = false) String category, @RequestParam(required = false) Double minPrice, @RequestParam(required = false) Double maxPrice, @RequestParam(defaultValue = "0") int page, @RequestParam(defaultValue = "10") int size) { Page<Product> result = productService.searchProducts(keyword, category, minPrice, maxPrice, page, size); return ResponseEntity.ok(result); } @PostMapping public ResponseEntity<Product> add(@RequestBody Product product) { product.setCreateTime(new Date()); product.setOnSale(true); return ResponseEntity.ok(productService.saveProduct(product)); } }

启动项目,访问:

GET /api/products?keyword=手机&category=数码&minPrice=2000&maxPrice=8000&page=0&size=10

你会发现,即使数据量达到百万级,响应时间也能稳定在50ms 以内


架构设计与避坑指南

光跑通还不够,上线前还得考虑稳定性、一致性、性能等问题。以下是我在真实项目中总结的经验。

🧩 数据同步怎么做?

ES 不是主库,数据来源通常是 MySQL。常见的同步方案有三种:

方案优点缺点
应用层双写(先写 DB 再写 ES)实现简单可能丢数据,事务难保证
Canal + Kafka 监听 Binlog异步解耦,可靠架构复杂,运维成本高
Logstash JDBC Input配置即可,适合离线同步实时性差

推荐做法:写操作走 MQ。例如:

  1. 用户新增商品 → 写入 MySQL;
  2. 发送消息到 Kafka(事件:product.created);
  3. 消费者拉取消息,更新 ES 索引;
  4. 失败则重试 + 死信队列告警。

这样既保证最终一致性,又不影响主流程性能。


🔍 索引设计最佳实践

别小看 mapping 设计,设计不好会导致查询慢、内存爆、甚至集群宕机。

1. 关闭动态映射(必须!)
PUT /product { "mappings": { "dynamic": "strict", "properties": { "title": { "type": "text", "analyzer": "ik_max_word" }, "category": { "type": "keyword" }, "price": { "type": "double" } } } }

设置"dynamic": "strict"后,任何意外字段都会报错,防止因脏数据导致 mapping explosion(映射爆炸),进而拖垮集群。

2. 合理设置分片数
"settings": { "number_of_shards": 3, "number_of_replicas": 1 }
  • 分片太多:资源开销大,查询合并慢;
  • 分片太少:无法水平扩展。

经验法则:单个分片不超过50GB,初始设为 3~5 个足够。


⚡ 性能优化技巧

1. Filter 比 Query 更快

在 DSL 中,filter context不计算相关性得分,且结果可缓存。适合用于onSale=truecategory=数码这类条件。

Spring Data 默认生成的是query,但我们可以通过NativeQuery手动控制:

Query query = NativeQuery.builder() .withQuery(q -> q.match(m -> m.field("title").query(keyword))) .withFilter(f -> f.term(t -> t.field("onSale").value(true))) .withPageable(pageable) .build();

实测性能提升可达 20%~40%。

2. 禁止深度分页

不要让用户翻到第 1000 页。from + size超过 1 万条时,性能急剧下降。

替代方案:search_after

// 记录上次最后一条的 sort 值,作为下次起点 SearchAfter searchAfter = new SearchAfter(Arrays.asList(lastSortValue)); Pageable pageable = PageRequest.of(0, 10, Sort.by("price").asc()).first().next(searchAfter);

适用于无限滚动场景,性能稳定。


写在最后:这不是终点,而是起点

当你第一次看到/api/products?keyword=手机在 30ms 内返回结果时,你会意识到:这才是现代系统的该有的样子

但这只是第一步。后续你还可以继续深入:

  • 给搜索结果加高亮显示
  • 实现拼音搜索(“xiangji” 能搜到 “相机”);
  • 添加搜索建议拼写纠错
  • 结合机器学习做个性化排序
  • 用 Kibana 做搜索行为分析

而这一切的基础,正是今天我们搭建的这套Spring Boot + Elasticsearch搜索引擎骨架。

如果你正在做一个电商项目、内容平台,或者只是想提升自己的技术栈,那么掌握这套组合拳,绝对值得投入时间。

💬 如果你在集成过程中遇到了问题,比如连接失败、分词无效、查询为空……欢迎留言讨论,我可以帮你一起排查。毕竟,每一个报错背后,都藏着一次成长的机会。

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

10、视觉显著对象跟踪应用开发与原理剖析

视觉显著对象跟踪应用开发与原理剖析 1. 应用规划 我们要开发的最终应用程序,会将视频序列中的每个RGB帧转换为显著图,提取所有有趣的原始对象,并将它们输入到均值漂移跟踪算法中。为实现这一目标,应用需要以下几个关键组件: - main :这是启动应用程序的主函数例程,…

作者头像 李华
网站建设 2026/3/31 10:46:52

14、交通标志识别与面部表情识别技术解析

交通标志识别与面部表情识别技术解析 交通标志识别 在交通标志识别任务中,一个优秀分类器的目标是使混淆矩阵呈对角化,这意味着每个样本的真实类别(c_true)和预测类别(c_pred)相同。其中,一对一(one - vs - one)策略结合HOG特征表现出色,从得到的混淆矩阵可以看出,…

作者头像 李华
网站建设 2026/3/31 20:43:23

BongoCat终极指南:打造专属桌面互动萌宠的完整方案

BongoCat终极指南&#xff1a;打造专属桌面互动萌宠的完整方案 【免费下载链接】BongoCat 让呆萌可爱的 Bongo Cat 陪伴你的键盘敲击与鼠标操作&#xff0c;每一次输入都充满趣味与活力&#xff01; 项目地址: https://gitcode.com/gh_mirrors/bong/BongoCat 想要让单调…

作者头像 李华
网站建设 2026/3/15 14:52:59

Charticulator终极实战:用约束求解突破传统图表限制

Charticulator终极实战&#xff1a;用约束求解突破传统图表限制 【免费下载链接】charticulator Interactive Layout-Aware Construction of Bespoke Charts 项目地址: https://gitcode.com/gh_mirrors/ch/charticulator 还在为Excel图表不够灵活而烦恼吗&#xff1f;&a…

作者头像 李华
网站建设 2026/3/25 15:25:06

MusicFree桌面歌词终极指南:3步解决所有显示问题

MusicFree桌面歌词终极指南&#xff1a;3步解决所有显示问题 【免费下载链接】MusicFree 插件化、定制化、无广告的免费音乐播放器 项目地址: https://gitcode.com/maotoumao/MusicFree MusicFree作为一款优秀的插件化音乐播放器&#xff0c;其桌面歌词功能为用户提供了…

作者头像 李华
网站建设 2026/4/1 8:27:16

抖音视频去水印终极指南:F2开源工具快速上手全攻略

还在为抖音视频的水印烦恼吗&#xff1f;想要轻松保存喜欢的短视频却苦于没有合适的工具&#xff1f;今天为你揭秘F2开源工具的完整使用教程&#xff0c;这款基于Python开发的抖音下载工具将彻底改变你的下载体验&#xff01; 【免费下载链接】TikTokDownload 抖音去水印批量下…

作者头像 李华