Spring Boot + Elasticsearch 实现全文搜索功能(商品搜索)!让搜索快如闪电

全文搜索,核心就三点:建索引、存数据、查数据。今天我就手把手教你,用 Spring Boot + Elasticsearch 实现一个能用的商品搜索!"

一、准备工作(只需 3 分钟)

你需要:

  • JDK 17
  • IDEA
  • Elasticsearch 8.0+(本地或 Docker)
  • Kibana(可选,用于可视化)

💡 没有 Elasticsearch? 用 Docker 一行命令启动:

bash 复制代码
docker run -d -p 9200:9200 -p 9300:9300 -e "discovery.type=single-node" --name elasticsearch elasticsearch:8.9.0

二、Elasticsearch 核心概念(简单理解)

三个核心概念:

  • Index(索引) :相当于数据库(如:products
  • Document(文档):相当于一行记录(如:商品信息)
  • Field(字段) :相当于列(如:namedescription

搜索类型:

  • 精确匹配term 查询
  • 全文搜索match 查询(支持分词)
  • 模糊搜索wildcardfuzzy 查询

三、Spring Boot 集成 Elasticsearch(完整代码)

步骤 1:添加依赖(pom.xml)

XML 复制代码
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    
    <!-- Elasticsearch Starter -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
    </dependency>
    
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
    </dependency>
</dependencies>

步骤 2:配置 Elasticsearch(application.yml)

XML 复制代码
spring:
  elasticsearch:
    uris: http://localhost:9200
    username: elastic  # 👈 从 Docker 日志获取
    password: your-elastic-password  # 👈 从 Docker 日志获取
    
  # Jackson 配置(用于序列化)
  jackson:
    time-zone: GMT+8
    date-format: yyyy-MM-dd HH:mm:ss

# 产品索引配置
es:
  index:
    product: products  # 索引名称

步骤 3:创建商品实体类(entity/Product.java)

java 复制代码
package com.example.esdemo.entity;

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;

@Document(indexName = "#{@environment.getProperty('es.index.product')}")
public class Product {

    @Id
    private String id;

    @Field(type = FieldType.Text, analyzer = "ik_smart", searchAnalyzer = "ik_smart")
    private String name;

    @Field(type = FieldType.Text, analyzer = "ik_smart", searchAnalyzer = "ik_smart")
    private String description;

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

    @Field(type = FieldType.Double)
    private Double price;

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

    // 构造函数(省略)
    public Product() {}
    public Product(String name, String description, String category, Double price, Integer stock) {
        this.name = name;
        this.description = description;
        this.category = category;
        this.price = price;
        this.stock = stock;
    }

    // getter/setter...
    public String getId() { return id; }
    public void setId(String id) { this.id = id; }
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    public String getDescription() { return description; }
    public void setDescription(String description) { this.description = description; }
    public String getCategory() { return category; }
    public void setCategory(String category) { this.category = category; }
    public Double getPrice() { return price; }
    public void setPrice(Double price) { this.price = price; }
    public Integer getStock() { return stock; }
    public void setStock(Integer stock) { this.stock = stock; }
}

注解说明

  • @Document:指定索引名称
  • @Field(type = FieldType.Text):文本字段,支持全文搜索
  • analyzer = "ik_smart":使用中文分词器(需提前安装)

步骤 4:创建 Repository(repository/ProductRepository.java)

java 复制代码
package com.example.esdemo.repository;

import com.example.esdemo.entity.Product;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
import org.springframework.stereotype.Repository;

import java.util.List;

@Repository
public interface ProductRepository extends ElasticsearchRepository<Product, String> {

    /**
     * 根据商品名称搜索(全文搜索)
     */
    List<Product> findByNameContaining(String name);

    /**
     * 根据分类和价格范围搜索
     */
    List<Product> findByCategoryAndPriceBetween(String category, Double minPrice, Double maxPrice);

    /**
     * 自定义查询方法(按 ID 查询)
     */
    Product findById(String id);
}

步骤 5:创建搜索服务(service/SearchService.java)

java 复制代码
package com.example.esdemo.service;

import com.example.esdemo.entity.Product;
import com.example.esdemo.repository.ProductRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class SearchService {

    @Autowired
    private ProductRepository productRepository;

    /**
     * 全文搜索商品(按名称和描述)
     */
    public List<Product> searchProducts(String keyword, int page, int size) {
        // 使用原生查询实现更复杂的搜索
        // 这里简化,使用 Repository 的自带方法
        return productRepository.findByNameContaining(keyword, PageRequest.of(page, size, Sort.by("price").ascending()));
    }

    /**
     * 分类筛选搜索
     */
    public List<Product> searchByCategory(String category, Double minPrice, Double maxPrice, int page, int size) {
        return productRepository.findByCategoryAndPriceBetween(
            category, minPrice, maxPrice, 
            PageRequest.of(page, size, Sort.by("price").ascending())
        );
    }

    /**
     * 保存商品到 ES
     */
    public Product saveProduct(Product product) {
        return productRepository.save(product);
    }

    /**
     * 批量保存商品
     */
    public Iterable<Product> saveAllProducts(List<Product> products) {
        return productRepository.saveAll(products);
    }

    /**
     * 删除商品
     */
    public void deleteProduct(String id) {
        productRepository.deleteById(id);
    }
}

步骤 6:创建搜索控制器(controller/SearchController.java)

java 复制代码
package com.example.esdemo.controller;

import com.example.esdemo.entity.Product;
import com.example.esdemo.service.SearchService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.List;
import java.util.Map;

@RestController
@RequestMapping("/api/search")
public class SearchController {

    @Autowired
    private SearchService searchService;

    /**
     * 全文搜索
     */
    @GetMapping
    public Map<String, Object> search(@RequestParam String keyword,
                                     @RequestParam(defaultValue = "0") int page,
                                     @RequestParam(defaultValue = "10") int size) {
        List<Product> products = searchService.searchProducts(keyword, page, size);
        return Map.of("success", true, "data", products, "total", products.size());
    }

    /**
     * 分类筛选搜索
     */
    @GetMapping("/filter")
    public Map<String, Object> searchByFilter(@RequestParam String category,
                                             @RequestParam(required = false) Double minPrice,
                                             @RequestParam(required = false) Double maxPrice,
                                             @RequestParam(defaultValue = "0") int page,
                                             @RequestParam(defaultValue = "10") int size) {
        minPrice = minPrice != null ? minPrice : 0.0;
        maxPrice = maxPrice != null ? maxPrice : Double.MAX_VALUE;
        
        List<Product> products = searchService.searchByCategory(category, minPrice, maxPrice, page, size);
        return Map.of("success", true, "data", products, "total", products.size());
    }

    /**
     * 批量导入测试数据
     */
    @PostMapping("/import")
    public String importTestData() {
        Product p1 = new Product("iPhone 15", "最新款苹果手机,A17芯片,超视网膜XDR显示屏", "手机", 5999.0, 100);
        Product p2 = new Product("MacBook Pro", "专业笔记本电脑,M3芯片,Liquid视网膜XDR显示屏", "电脑", 12999.0, 50);
        Product p3 = new Product("AirPods Pro", "无线降噪耳机,自适应通透模式", "耳机", 1999.0, 200);
        Product p4 = new Product("iPad Air", "轻薄平板电脑,M2芯片,11英寸Liquid视网膜显示屏", "平板", 4599.0, 80);
        
        searchService.saveAllProducts(List.of(p1, p2, p3, p4));
        return "测试数据导入成功!";
    }
}

四、运行验证(30 秒搞定)

1. 启动 Elasticsearch

确保 ES 已启动并能访问

2. 启动 Spring Boot 项目

bash 复制代码
mvn spring-boot:run

3. 导入测试数据

bash 复制代码
POST http://localhost:8080/api/search/import

4. 测试搜索

java 复制代码
# 全文搜索
GET http://localhost:8080/api/search?keyword=苹果

# 分类筛选
GET http://localhost:8080/api/search/filter?category=手机&minPrice=5000&maxPrice=10000

预期结果

  • 搜索 "苹果" → 返回 iPhone 15(因为描述中有"苹果手机")
  • 分类筛选 → 返回符合条件的商品
  • 响应速度毫秒级!

✅ 对比传统数据库 LIKE 查询,速度提升 10 倍以上!

六、Bonus:中文分词配置(关键!)

1. 安装 IK 分词器

bash 复制代码
# 进入 ES 容器
docker exec -it elasticsearch /bin/bash

# 安装 IK 分词器(版本要匹配 ES 版本)
bin/elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v8.9.0/elasticsearch-analysis-ik-8.9.0.zip

# 重启 ES
docker restart elasticsearch

2. 验证分词效果

bash 复制代码
# 测试分词
POST /_analyze
{
  "analyzer": "ik_smart",
  "text": "最新款苹果手机"
}

七、进阶:高亮显示搜索结果

java 复制代码
// 在 Service 中添加高亮查询
public SearchHits<Product> searchWithHighlight(String keyword) {
    Query query = NativeQuery.builder()
        .withQuery(q -> q.match(m -> m.field("name").query(keyword)))
        .withHighlight(h -> h.fields(f -> f.field("name").preTags("<em>").postTags("</em>")))
        .build();
    
    return elasticsearchOperations.search(query, Product.class);
}

八、写在最后

Elasticsearch,看似复杂,实则是 索引 + 文档 + 查询 的组合。

记住三句话:

  • 中文搜索必须装 IK 分词器
  • 文本字段用 Text,精确匹配用 Keyword
  • 全文搜索比数据库 LIKE 快 10 倍

学会 Elasticsearch,你就掌握了 海量数据搜索的核心技能


互动时间

你用 Elasticsearch 做过什么搜索功能?是商品搜索?还是日志分析?

欢迎评论区分享!

下期预告

《Spring Boot + Prometheus + Grafana 实现系统监控告警》

相关推荐
t***26591 小时前
Springboot中使用Elasticsearch(部署+使用+讲解 最完整)
spring boot·elasticsearch·jenkins
h***59331 小时前
使用Canal将MySQL数据同步到ES(Linux)
linux·mysql·elasticsearch
9***P3341 小时前
Rust在网络中的Rocket
开发语言·后端·rust
G皮T2 小时前
【ELasticsearch】索引字段设置 “index”: false 的作用
大数据·elasticsearch·搜索引擎·全文检索·索引·index·检索
Wzx1980122 小时前
go聊天室
开发语言·后端·golang
chenyuhao20242 小时前
MySQL索引特性
开发语言·数据库·c++·后端·mysql
oouy2 小时前
《Java泛型:给你的代码装上“快递分拣系统”,再也不会拆出一双鞋!》
后端
Python私教2 小时前
别再瞎折腾 LangChain 了:从 0 到 1 搭建 RAG 知识库的架构决策实录
后端
微学AI2 小时前
openGauss在AI时代的向量数据库应用实践与技术演进深度解析
后端