Spring Elasticsearch按主文档嵌套文档得分总和排序问题修复
问题说明
实体结构
定义了Cafe、Meal两类ES文档,代码如下:
@Document(indexName = Indices.CAFE_INDEX) @Data public class CafeDocument { @Id @Field(type = FieldType.Keyword) private Long id; @Field(type = FieldType.Text) private String name; @Field(type = FieldType.Keyword) private String addressLine; @Field(type = FieldType.Nested) private List<MealDocument> meals = new ArrayList<>(); } @Document(indexName = Indices.MEAL_INDEX) @Data public class MealDocument { @Id @Field(type = FieldType.Keyword) private String id; @Field(type = FieldType.Text) private String name; @Field(type = FieldType.Keyword) private Long cafeId; @Field(type = FieldType.Keyword) private Long cafeId; // 重复定义字段 }
查询逻辑
原有查询逻辑如下,目标是返回结果按照「咖啡馆自身字段匹配得分 + 关联餐品匹配得分」的总和排序:
public SearchHits<CafeDocument> findCafeDocuments(String filter) throws DataAccessResourceFailureException { BoolQueryBuilder builder = QueryBuilders.boolQuery(); filter = filter.toLowerCase(); builder.must(QueryBuilders.boolQuery() .should(QueryBuilders.multiMatchQuery(filter) .field("name", 4) .field("addressLine", 2) .field("postalCode", 1) // CafeDocument中未定义该字段 .fuzziness(Fuzziness.ONE) ) .should(QueryBuilders.nestedQuery("meals", QueryBuilders.multiMatchQuery(filter) .field("meals.name", 30) .field("meals.description", 2) // MealDocument中未定义该字段 .fuzziness(Fuzziness.ONE), ScoreMode.Total) )); NativeSearchQueryBuilder qb = new NativeSearchQueryBuilder(); qb.withQuery(builder); return operations.search(qb.build(), CafeDocument.class, IndexCoordinates.of( Indices.CAFE_INDEX)); }
异常现象
查询可以正确过滤掉不匹配的咖啡馆,但无论怎么调整字段boost权重,返回结果的排序都不会变化。
问题原因
排序不生效是多个问题共同导致的:
- 实体类存在字段错误:
MealDocument重复定义了cafeId字段,同时查询中用到的postalCode、meals.description字段都没有在对应实体中定义,会导致映射和匹配逻辑异常。 - 嵌套字段映射不生效:虽然给
meals字段加了@Field(type = FieldType.Nested)注解,但如果没有重建索引,ES会默认将嵌套对象识别为object类型,该类型下nested查询无法隔离每个餐品的独立匹配得分,会出现得分计算错误,权重调整自然不生效。 - 未显式指定排序规则:部分版本的Spring Data Elasticsearch不会默认按
_score关联度得分降序排序,没有显式声明排序规则时会采用文档内置顺序,导致得分变化不会反映到排序结果上。
修复步骤
1. 修正实体类代码
删除重复字段,补全查询用到的缺失字段:
@Document(indexName = Indices.MEAL_INDEX) @Data public class MealDocument { @Id @Field(type = FieldType.Keyword) private String id; @Field(type = FieldType.Text) private String name; @Field(type = FieldType.Text) private String description; // 补全查询用到的餐品描述字段 @Field(type = FieldType.Keyword) private Long cafeId; // 删除重复的cafeId定义 } @Document(indexName = Indices.CAFE_INDEX) @Data public class CafeDocument { @Id @Field(type = FieldType.Keyword) private Long id; @Field(type = FieldType.Text) private String name; @Field(type = FieldType.Keyword) private String addressLine; @Field(type = FieldType.Keyword) private String postalCode; // 补全查询用到的邮编字段,如果不需要可以删掉查询中对应条件 @Field(type = FieldType.Nested) private List<MealDocument> meals = new ArrayList<>(); }
2. 重建索引同步数据
ES的字段映射创建后不支持修改类型,需要删除旧的CAFE_INDEX索引,启动应用让Spring Data Elasticsearch根据修正后的实体自动创建正确的nested类型映射,再重新全量导入咖啡馆数据。
3. 修正查询逻辑
显式声明按得分降序排序,设置最小匹配规则,保证得分是咖啡馆自身匹配和餐品匹配的得分总和:
public SearchHits<CafeDocument> findCafeDocuments(String filter) { BoolQueryBuilder rootBuilder = QueryBuilders.boolQuery(); String lowerFilter = filter.toLowerCase(); // 咖啡馆自身字段匹配 MultiMatchQueryBuilder cafeMatch = QueryBuilders.multiMatchQuery(lowerFilter) .field("name", 4) .field("addressLine", 2) .field("postalCode", 1) .fuzziness(Fuzziness.ONE); // 嵌套餐品匹配,ScoreMode.Total表示所有匹配餐品的得分累加 NestedQueryBuilder mealMatch = QueryBuilders.nestedQuery( "meals", QueryBuilders.multiMatchQuery(lowerFilter) .field("meals.name", 30) .field("meals.description", 2) .fuzziness(Fuzziness.ONE), ScoreMode.Total ); // 合并匹配条件,至少匹配一个条件即可返回,得分是所有匹配项的得分之和 BoolQueryBuilder matchQuery = QueryBuilders.boolQuery() .should(cafeMatch) .should(mealMatch) .minimumShouldMatch(1); rootBuilder.must(matchQuery); NativeSearchQuery query = new NativeSearchQueryBuilder() .withQuery(rootBuilder) // 显式指定按关联度得分降序排序 .withSort(SortBuilders.scoreSort().order(SortOrder.DESC)) .build(); return operations.search(query, CafeDocument.class, IndexCoordinates.of(Indices.CAFE_INDEX)); }
修复后调整字段boost值就可以正常改变排序结果,如果得分不符合预期,可以调用ES的explain接口查看单条文档的得分构成,再微调权重即可。
内容的提问来源于stack exchange,提问作者john2994
相关产品推荐
相关产品推荐

