目录
需求分析
项目环境
API接口实现
springboot + spring-boot-starter-data-elasticsearch(其中spring boot版本:2.1.6)
elasticsearch(版本:6.8.5)
这里环境搭建就不多赘述了,具体详看源代码
Spring Data Elasticsearch给我们提供了一系列的聚合查询方法
@Data
@Document(indexName = "test", type = "product")
//indexName索引名称 可以理解为数据库名 必须为小写 不然会报org.elasticsearch.indices.InvalidIndexNameException异常
//type类型 可以理解为表名
public class Product {
/*
* @Name: 商品Id
* @Example: 1
* @Description:
*/
private Long id;
/*
* @Name: 品牌名称
* @Example: 小米
* @Description:
*/
@Field(type = FieldType.Keyword)
private String brand;
/*
* @Name: 分类id (1-手机 | 2-笔记本)
* @Example:
* @Description:
*/
@Field(type = FieldType.Long)
private Long category;
/*
* @Name: 商品名称
* @Example: 小米9pro 5G版手机 钛银黑 12G+512G
* @Description:
*/
@Field(type = FieldType.Text)
private String name;
/*
* @Name: 商品规格
* @Example: [
{
"attrName": "颜色",
"attrValue": "钛银黑"
},
{
"attrName": "内存",
"attrValue": "512G"
}
]
* @Description: 这里一定要使用nested(嵌套类型)
* 【与object区别在于可以很好的处理数组对象的内部关系,在内部,嵌套对象将数组中的每个对象索引为单独的隐藏文档,这意味着可以独立于其他对象查询每个嵌套对象】
* 详见:https://blog.csdn.net/laoyang360/article/details/82950393
*/
@Field(type = FieldType.Nested)
private List attrs;
@Data
public static class Attr{
@Field(type = FieldType.Keyword)
private String attrName;
@Field(type = FieldType.Keyword)
private String attrValue;
}
public interface FieldName {
String ID = "id";
String NAME = "name";
String CATEGORY = "category";
String BRAND = "brand";
String ATTRS = "attrs";
String ATTRS_NAME = "attrs.attrName";
String ATTRS_VALUE = "attrs.attrValue";
}
}
@Data
public class QueryVO {
private Long id;
/*
* @Name: 品牌名称
* @Example: ["小米", "华为"]
* @Description:
*/
private List brands = Lists.newArrayList();
/*
* @Name: 分类id (1-手机 | 2-笔记本)
* @Example: 1
* @Description:
*/
private Long category;
/*
* @Name: 商品名称
* @Example: 小米9pro 5G版手机 钛银黑 12G+512G
* @Description:
*/
private String name;
/*
* @Name: 规格参数
* @Example: [
{
"attrName": "颜色",
"attrValues": ["黑色"]
},
{
"attrName": "内存",
"attrValues": ["64G", "128G"]
}
]
* @Description:
*/
private List attrs = Lists.newArrayList();
}
// 商品分页查询
@Override
public Page search(QueryVO query, int page, int size) {
Pageable pageable = PageRequest.of(page-1, size);
return productRepository.search(this.addFilters(query), pageable);
}
// 过滤条件
private BoolQueryBuilder addFilters(QueryVO query) {
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
List attrGroups = query.getAttrs();
if (attrGroups != null) {
// 多属性过滤查询,如(内存、颜色、屏幕尺寸、版本等等)
for (AggVO.AttrGroup attrGroup : attrGroups) {
BoolQueryBuilder attrBoolQuery = QueryBuilders.boolQuery();
attrBoolQuery.filter(QueryBuilders.matchQuery(Product.FieldName.ATTRS_NAME, attrGroup.getAttrName()));
attrBoolQuery.filter(QueryBuilders.termsQuery(Product.FieldName.ATTRS_VALUE, attrGroup.getAttrValues())); // 这里多值匹配用【termsQuery】注意区分【termQuery】
// 使用NestedQuery查询(嵌套对象的过滤查询)
NestedQueryBuilder nestedQueryBuilder = QueryBuilders.nestedQuery(Product.FieldName.ATTRS, attrBoolQuery, ScoreMode.None);
boolQueryBuilder.filter(nestedQueryBuilder);
}
}
if (query.getId() != null) {
// 商品Id查询
boolQueryBuilder.filter(QueryBuilders.matchQuery(Product.FieldName.ID, query.getId()).operator(Operator.AND));
}
if (!query.getBrands().isEmpty()) {
// 品牌查询
boolQueryBuilder.filter(QueryBuilders.termsQuery(Product.FieldName.BRAND, query.getBrands()));
}
if (query.getCategory() != null) {
// 分类查询
boolQueryBuilder.filter(QueryBuilders.matchQuery(Product.FieldName.CATEGORY, query.getCategory()).operator(Operator.AND));
}
if (StringUtils.isNotBlank(query.getName())) {
// 商品名称查询(这里暂时没做分词处理)
boolQueryBuilder.filter(QueryBuilders.matchQuery(Product.FieldName.NAME, query.getName()).operator(Operator.AND));
}
return boolQueryBuilder;
}
这里可以将filter查询替换成must查询,区别在于filter性能更好一些,must要进行打分评估,也就是说要进行_score,而filter则不会。
通过TermsAggregationBuilder、NestedAggregationBuilder来实现字段和内置嵌套对象字段的分组统计查询,具体代码如下:
// 筛选条件聚合查询接口
@Override
public AggVO agg(QueryVO query) {
Pageable pageable = PageRequest.of(0, 10);
//检索条件
BoolQueryBuilder boolQueryBuilder = this.addFilters(query);
final String BRAND_AGG = "brand_agg";
final String CATEGORY_AGG = "category_agg";
final String ATTR_AGG = "attr_agg";
final String ATTR_NAME_AGG = ATTR_AGG + "_name";
final String ATTR_VALUE_AGG = ATTR_AGG+"_value";
//聚合条件
// 品牌聚合(取命中最多的前500个品牌)
TermsAggregationBuilder brandAggBuilder = AggregationBuilders.terms(BRAND_AGG).field(Product.FieldName.BRAND).size(500);
// 分类聚合(取命中最多的前20个)
TermsAggregationBuilder categoryAggBuilder = AggregationBuilders.terms(CATEGORY_AGG).field(Product.FieldName.CATEGORY).size(20);
// 内置对象-规格聚合
NestedAggregationBuilder attrAggBuilder = AggregationBuilders.nested(ATTR_AGG, Product.FieldName.ATTRS);
// 先聚合规格名称
TermsAggregationBuilder attrNameAggBuilder = AggregationBuilders.terms(ATTR_NAME_AGG).field(Product.FieldName.ATTRS_NAME);
// 再将名称分组后聚合规格值(取命中最多的前7个规格,聚合分组规格后再在每个分组规格中取命中最多的前15个参数值)
attrNameAggBuilder.subAggregation(AggregationBuilders.terms(ATTR_VALUE_AGG).field(Product.FieldName.ATTRS_VALUE).size(15)).size(7);
attrAggBuilder.subAggregation(attrNameAggBuilder);
//构建查询
SearchQuery searchQuery = new NativeSearchQueryBuilder()
.withQuery(boolQueryBuilder) // 过滤条件
.addAggregation(brandAggBuilder) // 品牌聚合
.addAggregation(categoryAggBuilder) // 分类聚合
.addAggregation(attrAggBuilder) // 规格参数聚合
.withPageable(pageable) // 分页
.build();
AggregatedPage search = (AggregatedPage)productRepository.search(searchQuery);
AggVO aggVO = new AggVO();
// 符合条件的商品总数量
aggVO.setTotalNum(search.getTotalElements());
// 品牌聚合结果
Terms brandAgg = (Terms)search.getAggregation(BRAND_AGG);
for (Terms.Bucket bucket : brandAgg.getBuckets()) {
String brandName = bucket.getKeyAsString();
aggVO.getBrands().add(brandName);
}
// 分类聚合结果
Terms categoryAgg = (Terms)search.getAggregation(CATEGORY_AGG);
for (Terms.Bucket bucket : categoryAgg.getBuckets()) {
long categoryId = bucket.getKeyAsNumber().longValue();
aggVO.getCategories().add(categoryId);
}
// 规格参数聚合结果
InternalNested attrNested = (InternalNested)search.getAggregation(ATTR_AGG);
Map aggregationMap = attrNested.getAggregations().asMap();
Terms attrNameAgg = (Terms)aggregationMap.get(ATTR_NAME_AGG);
for (Terms.Bucket bucket : attrNameAgg.getBuckets()) {
String attrName = bucket.getKeyAsString();
AggVO.AttrGroup attrGroup = new AggVO.AttrGroup();
attrGroup.setAttrName(attrName);
Terms attrValueAgg = (Terms)bucket.getAggregations().asMap().get(ATTR_VALUE_AGG);
for (Terms.Bucket subBucket : attrValueAgg.getBuckets()) {
String attrValue = subBucket.getKeyAsString();
attrGroup.getAttrValues().add(attrValue);
}
aggVO.getAttrs().add(attrGroup);
}
search.getAggregation(CATEGORY_AGG);
return aggVO;
}
最后再附上源代码:https://gitee.com/wangsai1109/springboot-es.git