通过商品搜索场景说明Spring Data Elasticsearch的复杂查询实现和索引设计要点:
一、复杂查询实现方案(多条件聚合示例)
// 1. 组合查询条件
public interface ProductRepository extends ElasticsearchRepository<Product, String> {
@Query("{\"bool\": {
\"must\": [
{\"match\": {\"name\": \"?0\"}},
{\"range\": {\"price\": {\"gte\": ?1}}}
],
\"filter\": [{
\"term\": {\"category.id\": \"?2\"}
}]
}}")
Page<Product> searchComplex(String keyword, BigDecimal minPrice, String categoryId, Pageable pageable);
// 2. 聚合实现
default SearchResponse<Aggregation> getProductAggregations() {
CriteriaQuery query = new CriteriaQuery(new Criteria());
// 添加价格直方图聚合
query.addAggregation(AggregationBuilders.histogram("price_histogram")
.field("price")
.interval(1000)
.minDocCount(0));
// 添加分类统计聚合
query.addAggregation(AggregationBuilders.terms("category_stats")
.field("category.name.keyword"));
return elasticsearchOperations.search(query, Product.class);
}
}
二、索引映射设计原则(商品模型示例)
@Document(indexName = "products", createIndex = false)
@Setting(
settingPath = "/elasticsearch/product-settings.json"
)
public class Product {
@Id
private String id;
// 文本类型支持分词搜索
@Field(type = FieldType.Text, analyzer = "ik_max_word")
private String name;
// 数值类型用于范围查询
@Field(type = FieldType.Double)
private Double price;
// 嵌套类型处理对象关联
@Field(type = FieldType.Nested)
private Category category;
// 对象数组使用nested类型
@Field(type = FieldType.Nested)
private List<ProductAttribute> attributes;
// 精确匹配字段使用keyword
@Field(type = FieldType.Keyword)
private String sku;
}
// 分类实体
public static class Category {
@Field(type = FieldType.Keyword)
private String id;
@Field(type = FieldType.Text, analyzer = "ik_smart")
private String name;
}
// 商品属性(包含嵌套属性)
public static class ProductAttribute {
@Field(type = FieldType.Keyword)
private String name;
@Field(type = FieldType.Keyword)
private String value;
}
三、字段类型选择依据:
-
Text类型:
- 适用场景:商品名称、描述等需要全文检索的字段
- 搭配分析器:中文场景使用IK分词(ik_max_word/ik_smart)
- 支持功能:模糊查询、高亮显示、相关性评分
-
Keyword类型:
- 适用场景:SKU编码、状态标识等需要精确匹配的字段
- 特性:禁用分词,支持聚合、排序、term查询
- 典型用例:过滤条件(如品牌、分类)
-
Nested类型:
- 适用场景:商品属性、规格参数等对象数组
- 必要性:保持对象内部字段的独立关联性
- 查询特点:需要nested query处理嵌套文档
四、查询构建最佳实践
public SearchHits<Product> buildComplexQuery(SearchRequest request) {
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
// 品牌过滤(精确匹配)
if (StringUtils.hasText(request.getBrand())) {
boolQuery.filter(QueryBuilders.termQuery("brand.keyword", request.getBrand()));
}
// 价格范围(数值查询)
boolQuery.must(QueryBuilders.rangeQuery("price")
.gte(request.getMinPrice())
.lte(request.getMaxPrice()));
// 分类过滤(父子关联)
if (request.getCategoryId() != null) {
boolQuery.filter(QueryBuilders.termQuery("category.id", request.getCategoryId()));
}
// 属性过滤(嵌套查询)
if (!request.getAttributes().isEmpty()) {
NestedQueryBuilder nestedQuery = QueryBuilders.nestedQuery("attributes",
QueryBuilders.boolQuery()
.must(QueryBuilders.termQuery("attributes.name", "color"))
.must(QueryBuilders.termsQuery("attributes.value", request.getColors())),
ScoreMode.None);
boolQuery.filter(nestedQuery);
}
NativeSearchQuery searchQuery = new NativeSearchQueryBuilder()
.withQuery(boolQuery)
.build();
return elasticsearchOperations.search(searchQuery, Product.class);
}
五、索引配置建议(product-settings.json)
{
"index": {
"number_of_shards": 3,
"number_of_replicas": 1,
"analysis": {
"analyzer": {
"ik_smart": {
"type": "custom",
"tokenizer": "ik_smart"
},
"ik_max_word": {
"type": "custom",
"tokenizer": "ik_max_word"
}
}
},
"mapping": {
"nested_fields": {
"limit": 50
}
}
}
}
关键设计考虑因素:
- 分片策略:根据数据量预估设置(建议单分片不超过50GB)
- 分词优化:中文场景必须配置IK分词器
- 嵌套限制:控制nested字段数量防止性能下降
- 动态映射:通过dynamic_templates控制未定义字段类型
- 版本管理:建议通过alias实现零停机索引重建
实际开发中应结合具体业务需求,通过@Setting注解加载自定义分析器配置,使用@Field注解精确控制字段映射类型,并通过组合BoolQuery实现复杂查询条件。