SpringBoot + 全文检索 + ngram 分词 示例工程
项目简介
本项目是一个基于Spring Boot的中文模糊搜索示例工程,演示了如何使用MySQL的全文检索和ngram分词器来实现高性能、支持中文分词和错别字容错的搜索功能。
核心功能
- 全文检索 :使用MySQL的全文检索功能,提高搜索性能
- ngram分词 :使用MySQL的ngram分词器,支持中文分词
- 错别字容错 :结合编辑距离算法,支持错别字搜索
- 缓存优化 :使用Redis缓存搜索结果,提高响应速度
- 异步事件 :使用Spring事件机制,处理商品更新时的缓存清除
- 测试数据 :提供测试数据生成功能,方便测试搜索性能
技术栈
- Spring Boot 2.7.5
- Spring Web
- Spring Data JPA
- Spring Data Redis
- MySQL 8.0
- Lombok
工程结构
chinese-search-demo/
├── src/
│ ├── main/
│ │ ├── java/com/example/demo/
│ │ │ ├── config/ # 配置类
│ │ │ ├── controller/ # 控制器
│ │ │ ├── dto/ # 数据传输对象
│ │ │ ├── entity/ # 实体类
│ │ │ ├── event/ # 事件类
│ │ │ ├── repository/ # 仓库类
│ │ │ ├── service/ # 服务类
│ │ │ └── DemoApplication.java # 主类
│ │ └── resources/
│ │ └── application.yml # 配置文件
│ └── test/ # 测试类
├── pom.xml # Maven依赖
└── README.md # 说明文档
核心实现
1. 数据库设计
在MySQL中创建商品表,并为搜索字段创建全文索引:
CREATE TABLE `product` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '商品ID',
`name` varchar(255) NOT NULL COMMENT '商品名称',
`description` text COMMENT '商品描述',
`price` decimal(10,2) NOT NULL COMMENT '商品价格',
`stock` int(11) NOT NULL COMMENT '商品库存',
`category_id` bigint(20) NOT NULL COMMENT '分类ID',
`brand_id` bigint(20) NOT NULL COMMENT '品牌ID',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
KEY `idx_category_id` (`category_id`),
KEY `idx_brand_id` (`brand_id`),
FULLTEXT KEY `ft_name_description` (`name`, `description`) WITH PARSER ngram
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品表';
2. MySQL配置
在MySQL配置文件中添加以下配置,启用ngram分词器:
[mysqld]
# ngram分词器配置
ft_min_word_len=1
ft_ngram_token_size=2
3. 全文检索查询
使用MySQL的MATCH AGAINST语法进行全文检索:
@Query(value = "SELECT * FROM product WHERE MATCH(name, description) AGAINST(?1 IN BOOLEAN MODE) OR name LIKE CONCAT('%', ?1, '%') OR description LIKE CONCAT('%', ?1, '%') ORDER BY (CASE WHEN MATCH(name, description) AGAINST(?1 IN BOOLEAN MODE) > 0 THEN MATCH(name, description) AGAINST(?1 IN BOOLEAN MODE) ELSE 0 END) DESC LIMIT ?2 OFFSET ?3",
nativeQuery = true)
List<Product> searchWithFuzzy(String keyword, Integer limit, Integer offset);
4. 搜索服务
实现搜索逻辑,包括关键词处理、缓存管理等:
public SearchResult search(String keyword, Integer page, Integer size) {
// 参数校验
if (keyword == null || keyword.trim().isEmpty()) {
return new SearchResult(0, Collections.emptyList());
}
// 处理关键词
String processedKeyword = processKeyword(keyword);
// 计算分页参数
Integer limit = size != null ? size : 20;
Integer offset = (page != null && page > 0) ? (page - 1) * limit : 0;
// 生成缓存键
String cacheKey = "search:" + keyword + ":" + page + ":" + size;
// 尝试从缓存获取
SearchResult cachedResult = (SearchResult) redisTemplate.opsForValue().get(cacheKey);
if (cachedResult != null) {
return cachedResult;
}
// 执行搜索
long startTime = System.currentTimeMillis();
List<Product> products = productRepository.searchWithFuzzy(processedKeyword, limit, offset);
long endTime = System.currentTimeMillis();
// 构建结果
SearchResult result = new SearchResult();
result.setTotal(products.size());
result.setProducts(products);
result.setTimeCost((int) (endTime - startTime));
// 缓存结果,设置过期时间为10分钟
redisTemplate.opsForValue().set(cacheKey, result, 10, TimeUnit.MINUTES);
return result;
}
5. 事件机制
使用Spring事件机制,在商品更新时清除相关缓存:
// 发布商品更新事件
eventPublisher.publishEvent(new ProductUpdateEvent(savedProduct));
// 监听商品更新事件
@EventListener
public void handleProductUpdate(ProductUpdateEvent event) {
// 清除相关搜索缓存
Iterable<String> keys = redisTemplate.keys("search:*");
if (keys != null) {
long count = redisTemplate.delete(keys);
log.info("清除搜索缓存,删除 {} 个键", count);
}
}
快速开始
1. 环境准备
- JDK 11+
- Maven 3.6+
- MySQL 8.0+
- Redis 5.0+
2. 配置MySQL
- 修改MySQL配置文件,启用ngram分词器:
[mysqld]
# ngram分词器配置
ft_min_word_len=1
ft_ngram_token_size=2
- 重启MySQL服务
- 创建数据库和表:
CREATE DATABASE demo DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
USE demo;
-- 商品表
CREATE TABLE `product` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '商品ID',
`name` varchar(255) NOT NULL COMMENT '商品名称',
`description` text COMMENT '商品描述',
`price` decimal(10,2) NOT NULL COMMENT '商品价格',
`stock` int(11) NOT NULL COMMENT '商品库存',
`category_id` bigint(20) NOT NULL COMMENT '分类ID',
`brand_id` bigint(20) NOT NULL COMMENT '品牌ID',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
KEY `idx_category_id` (`category_id`),
KEY `idx_brand_id` (`brand_id`),
FULLTEXT KEY `ft_name_description` (`name`, `description`) WITH PARSER ngram
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品表';
3. 修改配置
修改application.yml文件中的数据库和Redis连接信息:
spring:
datasource:
url: jdbc:mysql://localhost:3306/demo?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: your_password
redis:
host: localhost
port: 6379
password:
database: 0
4. 构建项目
mvn clean package
5. 运行项目
java -jar target/chinese-search-demo-1.0.0.jar
6. 生成测试数据
curl -X POST http://localhost:8080/api/data/generate?count=10000
7. 测试搜索接口
7.1 测试正常搜索
curl "http://localhost:8080/api/search?keyword=iPhone 13"
响应示例:
{
"code": 200,
"message": "success",
"data": {
"total": 10,
"products": [
{
"id": 1,
"name": "Apple 手机 13 Pro",
"description": "Apple 手机 13 Pro,全新正品,假一赔十",
"price": 6999.00,
"stock": 100,
"categoryId": 1,
"brandId": 1,
"createTime": "2023-01-01T12:00:00",
"updateTime": "2023-01-01T12:00:00"
}
],
"timeCost": 15
}
}
7.2 测试大小写不敏感
curl "http://localhost:8080/api/search?keyword=iphone13"
7.3 测试错别字容错
curl "http://localhost:8080/api/search?keyword=ihpone 13"
7.4 测试中文分词
curl "http://localhost:8080/api/search?keyword=苹果13"
性能测试
1. 测试环境
- CPU: Intel Core i7-10700
- 内存: 16GB
- MySQL: 8.0
- Redis: 6.0
- 数据量: 10万条商品数据
2. 测试结果
| 搜索关键词 | MySQL LIKE | 全文检索 + ngram | 性能提升 |
|---|---|---|---|
| iPhone 13 | 320ms | 45ms | 86% |
| iphone13 | 350ms | 50ms | 86% |
| 苹果13 | 380ms | 55ms | 86% |
| ihpone 13 | 820ms | 65ms | 92% |
从测试结果可以看出,全文检索 + ngram分词方案的性能是传统MySQL LIKE查询的7-12倍,同时支持中文分词和错别字容错。
最佳实践
- 索引设计 :为搜索字段创建全文索引,使用ngram分词器
- 关键词处理 :对关键词进行大小写转换、空格处理等标准化操作
- 缓存优化 :使用Redis缓存搜索结果,提高响应速度
- 异步事件 :使用Spring事件机制,处理商品更新时的缓存清除
- 分页查询 :合理设置分页参数,避免一次性返回过多数据
- 监控告警 :监控搜索性能,及时发现和解决性能问题
- 安全防护 :对搜索关键词进行SQL注入和XSS过滤
注意事项
- 本示例使用了MySQL的全文检索和ngram分词器,需要MySQL 5.7+版本
- 本示例的ngram分词器配置为
ft_ngram_token_size=2,将中文文本按2个字符切分 - 本示例的缓存过期时间设置为10分钟,实际项目中可以根据业务需求调整
- 在高并发场景下,建议使用读写分离、分库分表等架构优化
- 对于千万级以上的数据,可能需要考虑使用Elasticsearch等专业搜索引擎
总结
本示例工程演示了如何使用Spring Boot + MySQL的全文检索和ngram分词器来实现高性能、支持中文分词和错别字容错的搜索功能。通过这套方案,我们可以在不增加系统复杂度的情况下,实现响应时间毫秒级的中文模糊搜索。
在实际项目中,可以根据具体的业务场景和性能要求,调整数据库的配置和架构,以达到最佳的搜索体验。
联系方式
- 服务端技术精选:关注公众号,回复"中文搜索",获取完整的代码示例和实现方案。
- 个人技术博客:www.jiangyi.space
标题:SpringBoot + 全文检索 + ngram 分词 示例工程
作者:jiangyi
地址:http://jiangyi.space/articles/2026/02/27/1772032361354.html
公众号:服务端技术精选
评论
0 评论