SpringBoot 整合 ElasticSearch,给搜索插上"光速翅膀"
传统搜索的痛点
在我们的日常开发工作中,经常会遇到这样的需求:
- 用户输入一个关键词,要在海量数据中找到相关信息
- 要支持模糊匹配、多字段搜索、高亮显示等功能
- 搜索结果要有合理的排序和分页
- 还要支持复杂的查询条件组合
如果用传统的关系型数据库来做搜索,你会发现性能越来越差,用户体验也越来越糟糕。特别是在数据量达到百万、千万级别时,普通的LIKE查询简直就是灾难。
解决方案思路
今天我们要解决的,就是如何用ElasticSearch来提升搜索性能,让它飞起来。
核心思路是:
- 全文检索:利用ES的倒排索引机制实现高效的文本搜索
- 分布式架构:利用ES天然的分布式特性处理海量数据
- 智能分析:利用ES的分析器实现更智能的搜索体验
- 实时搜索:数据变更后能快速反映到搜索结果中
技术选型
- SpringBoot:快速搭建应用
- ElasticSearch:全文搜索引擎
- Spring Data Elasticsearch:Spring生态集成
- Kibana:可视化管理和调试
核心实现思路
1. 环境搭建与配置
首先,我们需要配置ES连接:
# application.yml
spring:
elasticsearch:
uris: http://localhost:9200
connection-timeout: 5s
socket-timeout: 60s
data:
elasticsearch:
cluster-nodes: localhost:9300
cluster-name: elasticsearch
2. 数据实体映射
定义ES中的文档结构:
@Document(indexName = "products")
public class ProductDocument {
@Id
private String id;
@Field(type = FieldType.Text, analyzer = "ik_max_word")
private String name;
@Field(type = FieldType.Keyword)
private String category;
@Field(type = FieldType.Double)
private Double price;
@Field(type = FieldType.Text, analyzer = "ik_max_word")
private String description;
@Field(type = FieldType.Date)
private Date createTime;
// getter/setter...
}
3. 搜索服务实现
创建搜索服务类:
@Service
public class ProductSearchService {
@Autowired
private ElasticsearchRestTemplate elasticsearchTemplate;
@Autowired
private ProductRepository productRepository;
/**
* 简单关键词搜索
*/
public Page<ProductDocument> searchByKeyword(String keyword, Pageable pageable) {
// 构建查询条件
Criteria criteria = new Criteria("name").contains(keyword)
.or(new Criteria("description").contains(keyword));
CriteriaQuery query = new CriteriaQuery(criteria);
query.setPageable(pageable);
SearchHits<ProductDocument> searchHits =
elasticsearchTemplate.search(query, ProductDocument.class);
return SearchHitSupport.pageResultsFor(searchHits, pageable);
}
/**
* 复杂条件搜索
*/
public Page<ProductDocument> complexSearch(ProductSearchCriteria criteria, Pageable pageable) {
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
// 名称模糊匹配
if (StringUtils.hasText(criteria.getKeyword())) {
boolQuery.should(QueryBuilders.matchQuery("name", criteria.getKeyword())
.boost(2.0f)); // 提高名称匹配的权重
boolQuery.should(QueryBuilders.matchQuery("description", criteria.getKeyword()));
}
// 价格范围
if (criteria.getMinPrice() != null || criteria.getMaxPrice() != null) {
RangeQueryBuilder rangeQuery = QueryBuilders.rangeQuery("price");
if (criteria.getMinPrice() != null) {
rangeQuery.gte(criteria.getMinPrice());
}
if (criteria.getMaxPrice() != null) {
rangeQuery.lte(criteria.getMaxPrice());
}
boolQuery.filter(rangeQuery);
}
// 分类精确匹配
if (StringUtils.hasText(criteria.getCategory())) {
boolQuery.filter(QueryBuilders.termQuery("category.keyword", criteria.getCategory()));
}
// 构建搜索请求
NativeSearchQuery searchQuery = new NativeSearchQueryBuilder()
.withQuery(boolQuery)
.withPageable(pageable)
.build();
SearchHits<ProductDocument> searchHits =
elasticsearchTemplate.search(searchQuery, ProductDocument.class);
return SearchHitSupport.pageResultsFor(searchHits, pageable);
}
}
4. 实时同步机制
确保MySQL数据与ES索引保持同步:
@Component
@Slf4j
public class ProductSyncService {
@Autowired
private ProductRepository productRepository;
@Autowired
private ElasticsearchRestTemplate elasticsearchTemplate;
/**
* 新增或更新产品时同步到ES
*/
@EventListener
public void handleProductSaved(ProductSavedEvent event) {
Product product = event.getProduct();
ProductDocument document = convertToDocument(product);
elasticsearchTemplate.save(document);
log.info("产品数据已同步到ES: {}", product.getId());
}
/**
* 删除产品时同步删除ES记录
*/
@EventListener
public void handleProductDeleted(ProductDeletedEvent event) {
String productId = event.getProductId();
elasticsearchTemplate.delete(productId, ProductDocument.class);
log.info("产品数据已从ES删除: {}", productId);
}
/**
* 全量数据同步(用于初始化或修复)
*/
public void syncAllProducts() {
Pageable pageable = PageRequest.of(0, 1000);
Page<Product> page;
int pageNum = 0;
do {
page = productRepository.findAll(pageable);
List<ProductDocument> documents = page.getContent().stream()
.map(this::convertToDocument)
.collect(Collectors.toList());
if (!documents.isEmpty()) {
elasticsearchTemplate.save(documents);
log.info("同步第{}页数据,共{}条", pageNum++, documents.size());
}
pageable = page.nextPageable();
} while (page.hasNext());
log.info("全量数据同步完成");
}
private ProductDocument convertToDocument(Product product) {
ProductDocument document = new ProductDocument();
document.setId(product.getId());
document.setName(product.getName());
document.setCategory(product.getCategory());
document.setPrice(product.getPrice());
document.setDescription(product.getDescription());
document.setCreateTime(product.getCreateTime());
return document;
}
}
5. 搜索API接口
提供RESTful API供前端调用:
@RestController
@RequestMapping("/api/search")
public class SearchController {
@Autowired
private ProductSearchService productSearchService;
@GetMapping("/products")
public Result<Page<ProductDocument>> searchProducts(
@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) {
ProductSearchCriteria criteria = new ProductSearchCriteria();
criteria.setKeyword(keyword);
criteria.setCategory(category);
criteria.setMinPrice(minPrice);
criteria.setMaxPrice(maxPrice);
Pageable pageable = PageRequest.of(page, size);
Page<ProductDocument> result = productSearchService.complexSearch(criteria, pageable);
return Result.success(result);
}
/**
* 搜索建议(自动补全)
*/
@GetMapping("/suggest")
public Result<List<String>> suggest(@RequestParam String keyword) {
// 使用ES的completion suggester实现自动补全
CompletionQueryBuilder completionQuery =
new CompletionQueryBuilder("name.suggest", keyword);
NativeSearchQuery searchQuery = new NativeSearchQueryBuilder()
.withQuery(completionQuery)
.withPageable(PageRequest.of(0, 10))
.build();
SearchHits<ProductDocument> searchHits =
elasticsearchTemplate.search(searchQuery, ProductDocument.class);
List<String> suggestions = searchHits.getSearchHits().stream()
.map(hit -> hit.getContent().getName())
.distinct()
.limit(10)
.collect(Collectors.toList());
return Result.success(suggestions);
}
}
性能优化策略
1. 索引优化
// 设置索引模板以优化性能
@PostConstruct
public void setupIndexTemplate() {
IndexOperations indexOps = elasticsearchTemplate.indexOps(ProductDocument.class);
if (!indexOps.exists()) {
indexOps.createWithMapping();
// 设置索引设置
Map<String, Object> settings = new HashMap<>();
settings.put("number_of_shards", 3);
settings.put("number_of_replicas", 1);
settings.put("refresh_interval", "30s"); // 提高写入性能
indexOps.updateSettings(settings);
}
}
2. 查询优化
- 使用Filter Context而非Query Context来过滤数据
- 合理设置分页大小,避免深度分页
- 使用聚合查询替代多次独立查询
3. 缓存策略
@Service
public class CachedSearchService {
@Cacheable(value = "productSearch", key = "#keyword + '_' + #pageable.pageNumber")
public Page<ProductDocument> searchWithCache(String keyword, Pageable pageable) {
// 带缓存的搜索实现
return productSearchService.searchByKeyword(keyword, pageable);
}
}
优势分析
相比传统数据库搜索,ES方案的优势显而易见:
- 性能卓越:毫秒级响应,即使面对亿级数据
- 功能丰富:支持复杂查询、聚合分析、相关性排序等
- 扩展性强:天然分布式架构,易于水平扩展
- 生态完善:丰富的分析器、插件和可视化工具
注意事项
- 数据一致性:ES与数据库之间可能存在短暂不一致
- 资源消耗:ES对内存和磁盘要求较高
- 运维复杂:需要专门的ES集群运维知识
- 成本考量:云服务费用可能较高
总结
通过SpringBoot整合ElasticSearch,我们可以轻松构建出高性能的搜索功能。ES的强大之处不仅在于搜索速度,更在于它提供了丰富的分析和查询能力,能够满足各种复杂的业务场景。
在实际项目中,建议根据具体需求选择合适的部署方案,并做好监控和运维工作。搜索功能作为用户体验的关键环节,值得我们投入足够的精力去优化和完善。
服务端技术精选,专注分享后端开发实战技术,助力你的技术成长!
标题:SpringBoot 整合 ElasticSearch,给搜索插上"光速翅膀"
作者:jiangyi
地址:http://jiangyi.space/articles/2026/01/08/1767868407740.html
0 评论