news 2026/6/26 22:47:10

手把手教程: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/6/16 17:43:20

如何用PaddlePaddle实现图像分割任务?U-Net实战教学

如何用PaddlePaddle实现图像分割任务&#xff1f;U-Net实战教学 在医学影像诊断、工业质检或遥感分析中&#xff0c;我们常常需要精确识别图像中的特定区域——比如肿瘤边界、裂缝位置或植被覆盖范围。传统方法依赖人工标注和规则提取&#xff0c;效率低且泛化能力差。而如今&a…

作者头像 李华
网站建设 2026/6/25 13:44:13

Minecraft跨平台存档转换终极指南:Chunker让游戏世界无缝衔接

Minecraft跨平台存档转换终极指南&#xff1a;Chunker让游戏世界无缝衔接 【免费下载链接】Chunker Convert Minecraft worlds between Java Edition and Bedrock Edition 项目地址: https://gitcode.com/gh_mirrors/chu/Chunker 还在为不同设备间的Minecraft存档无法互…

作者头像 李华
网站建设 2026/6/12 23:12:06

3步解锁键盘潜能:从普通用户到效率大师的终极指南

3步解锁键盘潜能&#xff1a;从普通用户到效率大师的终极指南 【免费下载链接】kmonad An advanced keyboard manager 项目地址: https://gitcode.com/gh_mirrors/km/kmonad 你是否曾因频繁切换Escape键而感到手指疲惫&#xff1f;是否觉得Caps Lock键占据了宝贵的位置却…

作者头像 李华
网站建设 2026/6/22 11:04:29

FastDFS-Client 终极使用指南:轻松构建分布式文件存储系统

在当今大数据时代&#xff0c;如何高效存储和管理海量文件成为每个开发者必须面对的挑战。FastDFS-Client作为Java平台上的分布式文件系统客户端&#xff0c;提供了简单易用的API接口&#xff0c;让开发者能够快速集成高性能的文件存储解决方案。 【免费下载链接】FastDFS_Clie…

作者头像 李华
网站建设 2026/6/22 14:18:49

Weblate术语库管理实战指南:从问题诊断到精准解决方案

Weblate术语库管理实战指南&#xff1a;从问题诊断到精准解决方案 【免费下载链接】weblate Web based localization tool with tight version control integration. 项目地址: https://gitcode.com/gh_mirrors/we/weblate Weblate作为基于Web的本地化工具&#xff0c;其…

作者头像 李华
网站建设 2026/6/13 15:33:08

从零实现企业级安全防护:Elasticsearch设置密码流程

从零构建企业级安全防线&#xff1a;手把手实现 Elasticsearch 密码认证与加密通信你有没有遇到过这样的场景&#xff1f;刚部署完一个 Elasticsearch 集群&#xff0c;准备接入 Kibana 做可视化分析&#xff0c;结果一运行curl http://localhost:9200&#xff0c;发现连密码都…

作者头像 李华