首页 > 解决方案 > 如何在单个字段上创建 Spring Data MongoDB GroupOperation,但结果 id 为对象?

问题描述

这是我要根据命名对象创建的组操作。

private static GroupOperation createStatsGroupOperationFromNomenclature(Nomenclature nomenclature) {
    Fields groupFields = Fields.fields("departmentCode");
    nomenclature.getDepartmentCode().ifPresent(code -> groupFields.and("subDepartmentCode"));
    nomenclature.getSubDepartmentCode().ifPresent(code -> groupFields.and("categoryCode"));
    nomenclature.getCategoryCode().ifPresent(code -> groupFields.and("subCategoryCode"));
    return group(groupFields)
        .count().as("nbProducts")
        .sum("$proposedMatchesAmount").as("nbProposedMatches")
        .sum("$reviewedMatchesAmount").as("nbReviewedMatches");
}

departmentCode如果我在 nomenclature 参数中提供 a和 a ,则使用前面的函数subDepartmentCode,这是执行的 mongo 查询:

{
  _id: {
    "departmentCode": "$departmentCode",
    "subDepartmentCode": "$subDepartmentCode"
  },
  "nbProduct": {
    $sum: 1
  },
  "proposedMatchesAmount": {
    $sum: "$proposedMatchesAmount"
  },
  "reviewedMatchesAmount": {
    $sum: "$reviewedMatchesAmount"
  }
}

此查询的结果在以下对象中解析:

@Builder
@Value
public class ProductsStatsDocument {
  @Id
  Nomenclature nomenclature;
  Integer nbProducts;
  Integer nbProposedMatches;
  Integer nbReviewedMatches;
}

当我只departmentCode在命名参数中提供一个时,问题就会出现。然后构建的组操作具有以下 mongo 查询语言等效项:

{
  _id: "$departmentCode",
  "nbProduct": {
    $sum: 1
  },
  "proposedMatchesAmount": {
    $sum: "$proposedMatchesAmount"
  },
  "reviewedMatchesAmount": {
    $sum: "$reviewedMatchesAmount"
  }
}

并且此查询的结果无法解析为之前ProductsStatsDocument的结果,因为结果_id字段 id 现在是 String 而不是 Nomenclature 对象。

即使只有一个字段,是否可以强制group方法使用对象作为结果_id字段?或者有没有其他方法来建立这样一个 mongo 组操作?

==================================================== ================

找到了这个问题的“原因”。这是一段来自 spring 数据的代码,它将 GroupOperation 转换为 bson 对象:

    } else if (this.idFields.exposesSingleNonSyntheticFieldOnly()) {
      FieldReference reference = context.getReference((Field)this.idFields.iterator().next());
      operationObject.put("_id", reference.toString());
    } else {

这是exposesSingleNonSyntheticFieldOnly方法:

  boolean exposesSingleNonSyntheticFieldOnly() {
    return this.originalFields.size() == 1;
  }

正如你所看到的,只要只有一个字段可以分组,它就会被用作_id结果值。

标签: mongodbspring-data-mongodb

解决方案


因此,目前似乎可行的最终解决方案是创建一个自定义 AggregationOperation 来管理文档转换_id部分:

public class ProductsStatsGroupOperation implements AggregationOperation {

  private static GroupOperation getBaseGroupOperation() {
    return group()
        .count().as("nbProducts")
        .sum("$proposedMatchesAmount").as("nbProposedMatches")
        .sum("$reviewedMatchesAmount").as("nbReviewedMatches");
  }

  private final Nomenclature nomenclature;

  public ProductsStatsGroupOperation(Nomenclature nomenclature) {
    this.nomenclature = nomenclature;
  }

  @Override
  public Document toDocument(AggregationOperationContext context) {
    Document groupOperation = getBaseGroupOperation().toDocument(context);

    Document operationId = new Document();
    for (Field field : getFieldsToGroupOn()) {
      FieldReference reference = context.getReference(field);
      operationId.put(field.getName(), reference.toString());
    }
    ((Document)groupOperation.get("$group")).put("_id", operationId);

    return groupOperation;
  }

  private Fields getFieldsToGroupOn() {
    Fields groupFields = Fields.fields("departmentCode");
    if (nomenclature.getDepartmentCode().isPresent()) {
      groupFields = groupFields.and("subDepartmentCode");
    }
    if (nomenclature.getSubDepartmentCode().isPresent()) {
      groupFields = groupFields.and("categoryCode");
    }
    if (nomenclature.getCategoryCode().isPresent()) {
      groupFields = groupFields.and("subCategoryCode");
    }
    return groupFields;
  }
}

这个解决方案有一个不好的地方:toDocument似乎不推荐使用覆盖的方法。


推荐阅读