此版本仍在开发中,尚未被视为稳定版。如需最新稳定版本,请使用 Spring Data MongoDB 5.0.4spring-doc.cadn.net.cn

聚合框架支持

Spring Data MongoDB 提供了对 MongoDB 2.2 版本中引入的聚合框架(Aggregation Framework)的支持。spring-doc.cadn.net.cn

如需更多信息,请参阅 MongoDB 聚合框架及其他数据聚合工具的完整参考文档spring-doc.cadn.net.cn

基本概念

Spring Data MongoDB 中的聚合框架支持基于以下关键抽象:AggregationAggregationResultsspring-doc.cadn.net.cn

  • Aggregationspring-doc.cadn.net.cn

    Aggregation 表示一个 MongoDB aggregate 操作,并包含聚合管道指令的描述。通过调用 newAggregation(…) 类的相应 Aggregation 静态工厂方法来创建聚合操作,该方法接收一个 AggregateOperation 列表以及一个可选的输入类。spring-doc.cadn.net.cn

    实际的聚合操作由 aggregateMongoTemplate 方法执行,该方法将所需的输出类作为参数。spring-doc.cadn.net.cn

  • TypedAggregationspring-doc.cadn.net.cn

    TypedAggregationAggregation 类似,包含聚合管道的指令以及对输入类型的引用,该引用用于将领域属性映射到实际的文档字段。spring-doc.cadn.net.cn

    在运行时,字段引用会根据给定的输入类型进行检查,并考虑可能存在的 @Field 注解。spring-doc.cadn.net.cn

自 3.2 版本起,引用不存在的属性将不再引发错误。若要恢复之前的行为,请使用 strictMappingAggregationOptions 选项。spring-doc.cadn.net.cn

  • AggregationDefinitionspring-doc.cadn.net.cn

    AggregationDefinition 表示一个 MongoDB 聚合管道操作,并描述在此聚合步骤中应执行的处理。尽管您可以手动创建一个 AggregationDefinition,但我们建议使用 Aggregate 类提供的静态工厂方法来构建 AggregateOperationspring-doc.cadn.net.cn

  • AggregationResultsspring-doc.cadn.net.cn

    AggregationResults 是聚合操作结果的容器。它提供了对原始聚合结果的访问,该结果以 Document 的形式呈现,并包含映射后的对象以及有关聚合的其他信息。spring-doc.cadn.net.cn

    以下示例展示了使用 Spring Data MongoDB 对 MongoDB 聚合框架支持的标准用法:spring-doc.cadn.net.cn

    import static org.springframework.data.mongodb.core.aggregation.Aggregation.*;
    
    Aggregation agg = newAggregation(
        pipelineOP1(),
        pipelineOP2(),
        pipelineOPn()
    );
    
    AggregationResults<OutputType> results = mongoTemplate.aggregate(agg, "INPUT_COLLECTION_NAME", OutputType.class);
    List<OutputType> mappedResult = results.getMappedResults();

请注意,如果您将输入类作为第一个参数传递给 newAggregation 方法,MongoTemplate 会从该类推导出输入集合的名称。否则,如果您未指定输入类,则必须显式提供输入集合的名称。如果同时提供了输入类和输入集合,则后者优先。spring-doc.cadn.net.cn

支持的聚合操作与阶段

MongoDB 聚合框架提供了以下类型的聚合阶段和操作:spring-doc.cadn.net.cn

不支持的聚合阶段(例如 MongoDB Atlas 的 $search)可以通过实现 AggregationOperation 来提供。 Aggregation.stage 是一个快捷方式,可通过提供聚合阶段的 JSON 或 Bson 表示形式来注册该阶段。spring-doc.cadn.net.cn

Aggregation.stage("""
    { $search : {
        "near": {
          "path": "released",
          "origin": { "$date": { "$numberLong": "..." } } ,
          "pivot": 7
        }
      }
    }
""");

截至本文撰写时,我们在 Spring Data MongoDB 中提供了对以下聚合操作符的支持:spring-doc.cadn.net.cn

表1. Spring Data MongoDB 当前支持的聚合操作符

集合聚合操作符spring-doc.cadn.net.cn

setEquals, setIntersection, setUnion, setDifference, setIsSubset, anyElementTrue, allElementsTruespring-doc.cadn.net.cn

分组/累加器聚合操作符spring-doc.cadn.net.cn

addToSet, bottom, bottomN, covariancePop, covarianceSamp, expMovingAvg, first, firstN, last, lastN max, maxN, min, minN, avg, push, sum, top, topN, count (*), median, percentile, stdDevPop, stdDevSampspring-doc.cadn.net.cn

算术聚合运算符spring-doc.cadn.net.cn

abs, acos, acosh, add (* via plus),asin, asin, atan, atan2, atanh, ceil, cos, cosh, derivative, divide, exp, floor, integral, ln, log, log10, mod, multiply, pow, round, sqrt, subtract (* via minus),sin, sinh, tan, tanh, truncspring-doc.cadn.net.cn

字符串聚合操作符spring-doc.cadn.net.cn

concat, substr, toLower, toUpper, strcasecmp, indexOfBytes, indexOfCP, regexFind, regexFindAll, regexMatch, replaceAll, replaceOne, split, strLenBytes, strLenCP, substrCP, trim, ltrim, rtimspring-doc.cadn.net.cn

比较聚合操作符spring-doc.cadn.net.cn

eq(通过 is)、gtgteltltenespring-doc.cadn.net.cn

数组聚合操作符spring-doc.cadn.net.cn

arrayElementAt, arrayToObject, concatArrays, filter, first, in, indexOfArray, isArray, last, range, reverseArray, reduce, size, sortArray, slice, zipspring-doc.cadn.net.cn

字面量运算符spring-doc.cadn.net.cn

literalspring-doc.cadn.net.cn

日期聚合操作符spring-doc.cadn.net.cn

dateSubstract, dateTrunc, dayOfYear, dayOfMonth, dayOfWeek, year, month, week, hour, minute, second, millisecond, dateAdd, dateDiff, dateToString, dateFromString, dateFromParts, dateToParts, isoDayOfWeek, isoWeek, isoWeekYear, tsIncrement, tsSecondspring-doc.cadn.net.cn

变量运算符spring-doc.cadn.net.cn

mapspring-doc.cadn.net.cn

条件聚合操作符spring-doc.cadn.net.cn

cond, ifNull, switchspring-doc.cadn.net.cn

类型聚合操作符spring-doc.cadn.net.cn

typespring-doc.cadn.net.cn

转换聚合操作符spring-doc.cadn.net.cn

convert, degreesToRadians, toBool, toDate, toDecimal, toDouble, toInt, toLong, toObjectId, toStringspring-doc.cadn.net.cn

对象聚合操作符spring-doc.cadn.net.cn

objectToArray, mergeObjects, getField, setFieldspring-doc.cadn.net.cn

脚本聚合操作符spring-doc.cadn.net.cn

function, accumulatorspring-doc.cadn.net.cn

* 该操作由 Spring Data MongoDB 映射或添加。spring-doc.cadn.net.cn

请注意,此处未列出的聚合操作目前不受 Spring Data MongoDB 支持。比较类聚合操作符以 Criteria 表达式的形式表示。spring-doc.cadn.net.cn

投影表达式

投影表达式用于定义特定聚合步骤的结果字段。可以通过Aggregation类的project方法来定义这些投影表达式,既可以传递一个String对象列表,也可以通过聚合框架的Fields对象来定义。通过使用and(String)方法和as(String)方法,可以使用面向对象的方式扩展投影,并对其进行别名化。 请注意,你也可以通过使用聚合框架中的Fields.field静态工厂方法来定义带有别名的字段,然后使用这些字段构建一个新的Fields实例。在后续的聚合阶段中只能引用包含字段或其别名(包括新定义的字段及其别名)的字段名称。未包含在投影中的字段在后续的聚合阶段中无法被引用。以下示例展示了投影表达式的使用方法:spring-doc.cadn.net.cn

示例 1. 投影表达式示例
// generates {$project: {name: 1, netPrice: 1}}
project("name", "netPrice")

// generates {$project: {thing1: $thing2}}
project().and("thing1").as("thing2")

// generates {$project: {a: 1, b: 1, thing2: $thing1}}
project("a","b").and("thing1").as("thing2")
示例2. 使用投影和排序的多阶段聚合
// generates {$project: {name: 1, netPrice: 1}}, {$sort: {name: 1}}
project("name", "netPrice"), sort(ASC, "name")

// generates {$project: {name: $firstname}}, {$sort: {name: 1}}
project().and("firstname").as("name"), sort(ASC, "name")

// does not work
project().and("firstname").as("name"), sort(ASC, "firstname")

更多关于投影操作的示例可以在 AggregationTests 类中找到。有关投影表达式的更多详细信息,请参阅 MongoDB 聚合框架参考文档中的相应章节spring-doc.cadn.net.cn

分面分类

从 3.4 版本起,MongoDB 通过使用聚合框架(Aggregation Framework)支持分面分类(faceted classification)。分面分类采用语义类别(可以是通用的或特定主题的),这些类别组合起来形成完整的分类条目。在聚合管道中流动的文档会被归类到不同的桶(buckets)中。多分面分类允许对同一组输入文档执行多种聚合操作,而无需多次检索输入文档。spring-doc.cadn.net.cn

存储桶

桶操作(Bucket operations)根据指定的表达式和桶边界,将传入的文档划分为若干组,称为“桶”(buckets)。桶操作需要一个分组字段或分组表达式。您可以通过 bucket() 类的 bucketAuto()Aggregate 方法来定义这些操作。BucketOperationBucketAutoOperation 可以基于聚合表达式对输入文档进行累积计算。您可以使用流畅 API(fluent API),通过 with…() 方法和 andOutput(String) 方法为桶操作添加额外参数。此外,还可以使用 as(String) 方法为该操作设置别名。每个桶在输出中都表示为一个文档。spring-doc.cadn.net.cn

BucketOperation 使用一组定义好的边界将传入的文档分组到这些类别中。边界必须是有序的。以下示例展示了一些桶操作:spring-doc.cadn.net.cn

示例3. 存储桶操作示例
// generates {$bucket: {groupBy: $price, boundaries: [0, 100, 400]}}
bucket("price").withBoundaries(0, 100, 400);

// generates {$bucket: {groupBy: $price, default: "Other" boundaries: [0, 100]}}
bucket("price").withBoundaries(0, 100).withDefault("Other");

// generates {$bucket: {groupBy: $price, boundaries: [0, 100], output: { count: { $sum: 1}}}}
bucket("price").withBoundaries(0, 100).andOutputCount().as("count");

// generates {$bucket: {groupBy: $price, boundaries: [0, 100], 5, output: { titles: { $push: "$title"}}}
bucket("price").withBoundaries(0, 100).andOutput("title").push().as("titles");

BucketAutoOperation 用于确定边界,以尝试将文档均匀地分配到指定数量的桶中。BucketAutoOperation 可选择性地接受一个粒度(granularity)值,该值指定用于计算边界的优选数值序列,以确保计算出的边界落在优选的整数或10的幂上。以下列表展示了桶操作的示例:spring-doc.cadn.net.cn

示例 4. 存储桶操作示例
// generates {$bucketAuto: {groupBy: $price, buckets: 5}}
bucketAuto("price", 5)

// generates {$bucketAuto: {groupBy: $price, buckets: 5, granularity: "E24"}}
bucketAuto("price", 5).withGranularity(Granularities.E24).withDefault("Other");

// generates {$bucketAuto: {groupBy: $price, buckets: 5, output: { titles: { $push: "$title"}}}
bucketAuto("price", 5).andOutput("title").push().as("titles");

要创建桶中的输出字段,桶操作可以通过 AggregationExpression 使用 andOutput() 方法,以及通过 #mongo.aggregation.projection.expressions 方法使用 SpEL 表达式spring-doc.cadn.net.cn

请注意,有关桶表达式的更多详细信息,请参阅 MongoDB 聚合框架参考文档中的 $bucket 部分$bucketAuto 部分spring-doc.cadn.net.cn

多维聚合

可以使用多个聚合管道在一个聚合阶段内创建多维度(或多方面)的聚合,以从多个维度对数据进行刻画。多维度聚合提供多种过滤器和分类方式,用于引导数据的浏览与分析。实现多维度聚合的一个常见示例是,许多在线零售商通过在产品价格、制造商、尺寸及其他因素上应用过滤器,帮助用户缩小搜索结果范围。spring-doc.cadn.net.cn

你可以通过使用 FacetOperation 类的 facet() 方法来定义一个 Aggregation。你可以使用 and() 方法为其添加多个聚合管道。每个子管道在输出文档中都有其自己的字段,用于将其结果以文档数组的形式存储。spring-doc.cadn.net.cn

子管道可以在分组之前对输入文档进行投影和过滤。常见用例包括在分类前提取日期部分或进行计算。以下列表展示了 facet 操作的示例:spring-doc.cadn.net.cn

示例5. Facet操作示例
// generates {$facet: {categorizedByPrice: [ { $match: { price: {$exists : true}}}, { $bucketAuto: {groupBy: $price, buckets: 5}}]}}
facet(match(Criteria.where("price").exists(true)), bucketAuto("price", 5)).as("categorizedByPrice"))

// generates {$facet: {categorizedByCountry: [ { $match: { country: {$exists : true}}}, { $sortByCount: "$country"}]}}
facet(match(Criteria.where("country").exists(true)), sortByCount("country")).as("categorizedByCountry"))

// generates {$facet: {categorizedByYear: [
//     { $project: { title: 1, publicationYear: { $year: "publicationDate"}}},
//     { $bucketAuto: {groupBy: $price, buckets: 5, output: { titles: {$push:"$title"}}}
// ]}}
facet(project("title").and("publicationDate").extractYear().as("publicationYear"),
      bucketAuto("publicationYear", 5).andOutput("title").push().as("titles"))
  .as("categorizedByYear"))

请注意,有关分面操作的更多详细信息,可以在 $facet 章节 的 MongoDB 聚合框架参考文档中找到。spring-doc.cadn.net.cn

按数量排序

按计数排序操作会根据指定表达式的值对传入文档进行分组,计算每个不同分组中文档的数量,并按计数对结果进行排序。它在使用多面分类(Faceted Classification)时提供了一个便捷的排序快捷方式。按计数排序操作需要一个分组字段或分组表达式。以下示例展示了一个按计数排序的操作:spring-doc.cadn.net.cn

示例 6. 按计数排序示例
// generates { $sortByCount: "$country" }
sortByCount("country");

按计数排序的操作等同于以下 BSON(二进制 JSON):spring-doc.cadn.net.cn

{ $group: { _id: <expression>, count: { $sum: 1 } } },
{ $sort: { count: -1 } }

投影表达式中的 Spring 表达式支持

我们通过 andExpressionProjectionOperation 类的 BucketOperation 方法,支持在投影表达式中使用 SpEL 表达式。此功能允许您将所需表达式定义为 SpEL 表达式。在执行查询时,SpEL 表达式会被转换为相应的 MongoDB 投影表达式部分。这种机制使得表达复杂计算变得更加容易。spring-doc.cadn.net.cn

使用 SpEL 表达式进行复杂计算

请考虑以下 SpEL 表达式:spring-doc.cadn.net.cn

1 + (q + 1) / (q - 1)

上述表达式被转换为以下投影表达式部分:spring-doc.cadn.net.cn

{ "$add" : [ 1, {
    "$divide" : [ {
        "$add":["$q", 1]}, {
        "$subtract":[ "$q", 1]}
    ]
}]}

您可以在聚合框架示例5聚合框架示例6中看到更多上下文中的示例。 您可以在SpelExpressionTransformerUnitTests中找到更多受支持的SpEL表达式构造的使用示例。spring-doc.cadn.net.cn

支持的 SpEL 转换

除了上表所示的转换之外,您还可以使用标准的 SpEL 操作(例如 new)来创建数组,并通过其名称引用表达式(后跟括号中的参数)。以下示例展示了如何以这种方式创建数组:spring-doc.cadn.net.cn

// { $setEquals : [$a, [5, 8, 13] ] }
.andExpression("setEquals(a, new int[]{5, 8, 13})");

聚合框架示例

本节中的示例演示了在 Spring Data MongoDB 中使用 MongoDB 聚合框架的模式。spring-doc.cadn.net.cn

聚合框架示例 1

在这个入门示例中,我们要对一个标签列表进行聚合,以获取 MongoDB 集合(名为 tags)中某个特定标签的出现次数,并按出现次数降序排序。该示例演示了分组、排序、投影(字段选择)和展开(结果拆分)的用法。spring-doc.cadn.net.cn

class TagCount {
 String tag;
 int n;
}
import static org.springframework.data.mongodb.core.aggregation.Aggregation.*;

Aggregation agg = newAggregation(
    project("tags"),
    unwind("tags"),
    group("tags").count().as("n"),
    project("n").and("tag").previousOperation(),
    sort(DESC, "n")
);

AggregationResults<TagCount> results = mongoTemplate.aggregate(agg, "tags", TagCount.class);
List<TagCount> tagCount = results.getMappedResults();

上述列表使用了以下算法:spring-doc.cadn.net.cn

  1. 通过使用 newAggregation 静态工厂方法创建一个新的聚合操作,并向该方法传入一个聚合操作列表。这些聚合操作定义了我们 Aggregation 的聚合管道。spring-doc.cadn.net.cn

  2. 使用 project 操作从输入集合中选择 tags 字段(该字段是一个字符串数组)。spring-doc.cadn.net.cn

  3. 使用 unwind 操作为 tags 数组中的每个标签生成一个新文档。spring-doc.cadn.net.cn

  4. 使用 group 操作为每个 tags 值定义一个分组,并通过 count 聚合操作符对出现次数进行聚合,将结果收集到一个名为 n 的新字段中。spring-doc.cadn.net.cn

  5. 选择 n 字段,并为上一个分组操作生成的 ID 字段创建一个别名(因此调用 previousOperation()),别名为 tagspring-doc.cadn.net.cn

  6. 使用 sort 操作按标签出现次数的降序对生成的标签列表进行排序。spring-doc.cadn.net.cn

  7. 调用 aggregate 上的 MongoTemplate 方法,并将创建好的 Aggregation 作为参数传入,以让 MongoDB 执行实际的聚合操作。spring-doc.cadn.net.cn

请注意,输入集合被显式指定为 tags 方法的 aggregate 参数。如果未显式指定输入集合的名称,则会根据传递给 newAggreation 方法的第一个参数的输入类来推导得出。spring-doc.cadn.net.cn

聚合框架示例 2

此示例基于 MongoDB 聚合框架文档中的按州划分的最大与最小城市示例。我们添加了额外的排序,以确保在不同版本的 MongoDB 中都能产生稳定的结果。在此示例中,我们希望使用聚合框架返回每个州人口最多和最少的城市。该示例展示了分组、排序和投影(字段选择)操作。spring-doc.cadn.net.cn

class ZipInfo {
   String id;
   String city;
   String state;
   @Field("pop") int population;
   @Field("loc") double[] location;
}

class City {
   String name;
   int population;
}

class ZipInfoStats {
   String id;
   String state;
   City biggestCity;
   City smallestCity;
}
import static org.springframework.data.mongodb.core.aggregation.Aggregation.*;

TypedAggregation<ZipInfo> aggregation = newAggregation(ZipInfo.class,
    group("state", "city")
       .sum("population").as("pop"),
    sort(ASC, "pop", "state", "city"),
    group("state")
       .last("city").as("biggestCity")
       .last("pop").as("biggestPop")
       .first("city").as("smallestCity")
       .first("pop").as("smallestPop"),
    project()
       .and("state").previousOperation()
       .and("biggestCity")
          .nested(bind("name", "biggestCity").and("population", "biggestPop"))
       .and("smallestCity")
          .nested(bind("name", "smallestCity").and("population", "smallestPop")),
    sort(ASC, "state")
);

AggregationResults<ZipInfoStats> result = mongoTemplate.aggregate(aggregation, ZipInfoStats.class);
ZipInfoStats firstZipInfoStats = result.getMappedResults().get(0);

请注意,ZipInfo 类映射了给定输入集合的结构。ZipInfoStats 类定义了所需输出格式的结构。spring-doc.cadn.net.cn

前面的列表使用了以下算法:spring-doc.cadn.net.cn

  1. 使用 group 操作从输入集合中定义一个分组。分组条件是 statecity 字段的组合,该组合构成了分组的 ID 结构。我们使用 population 运算符对分组元素中的 sum 属性值进行聚合,并将结果保存在 pop 字段中。spring-doc.cadn.net.cn

  2. 使用 sort 操作按 popstatecity 字段对中间结果进行升序排序,使得最小的城市排在结果顶部,最大的城市排在结果底部。请注意,对 statecity 的排序实际上是隐式地针对分组 ID 字段进行的(由 Spring Data MongoDB 处理)。spring-doc.cadn.net.cn

  3. 再次使用 group 操作,按 state 对中间结果进行分组。注意,state 再次隐式引用了一个组 ID 字段。在 last(…) 操作中,我们分别通过调用 first(…​)project 操作符,选取了最大城市和最小城市的名称及其人口数量。spring-doc.cadn.net.cn

  4. 从上一个 state 操作中选择 group 字段。请注意,state 再次隐式引用了一个组 ID 字段。由于我们不希望出现隐式生成的 ID,因此通过使用 and(previousOperation()).exclude() 将该 ID 从上一个操作中排除。又因为我们希望在输出类中填充嵌套的 City 结构,所以必须通过使用 nested 方法来生成相应的子文档。spring-doc.cadn.net.cn

  5. StateStats 操作中,将生成的 sort 列表按州名升序排序。spring-doc.cadn.net.cn

请注意,我们从作为 ZipInfo 方法第一个参数传入的 newAggregation 类中推导出输入集合的名称。spring-doc.cadn.net.cn

聚合框架示例 3

此示例基于 MongoDB 聚合框架文档中的人口超过一千万的州示例。我们添加了额外的排序,以确保在不同版本的 MongoDB 中都能产生稳定的结果。在此示例中,我们希望使用聚合框架返回所有人口超过一千万的州。该示例展示了分组(grouping)、排序(sorting)和匹配(过滤,matching)操作。spring-doc.cadn.net.cn

class StateStats {
   @Id String id;
   String state;
   @Field("totalPop") int totalPopulation;
}
import static org.springframework.data.mongodb.core.aggregation.Aggregation.*;

TypedAggregation<ZipInfo> agg = newAggregation(ZipInfo.class,
    group("state").sum("population").as("totalPop"),
    sort(ASC, previousOperation(), "totalPop"),
    match(where("totalPop").gte(10 * 1000 * 1000))
);

AggregationResults<StateStats> result = mongoTemplate.aggregate(agg, StateStats.class);
List<StateStats> stateStatsList = result.getMappedResults();

前面的列表使用了以下算法:spring-doc.cadn.net.cn

  1. 根据 state 字段对输入集合进行分组,并计算 population 字段的总和,将结果存储在新字段 "totalPop" 中。spring-doc.cadn.net.cn

  2. 除了按 "totalPop" 字段升序排序外,还根据前一个分组操作的 id 引用对中间结果进行排序。spring-doc.cadn.net.cn

  3. 通过使用接受 match 查询作为参数的 Criteria 操作来过滤中间结果。spring-doc.cadn.net.cn

请注意,我们从作为第一个参数传递给 ZipInfo 方法的 newAggregation 类派生出输入集合的名称。spring-doc.cadn.net.cn

聚合框架示例 4

此示例演示了在投影操作中使用简单算术运算。spring-doc.cadn.net.cn

class Product {
    String id;
    String name;
    double netPrice;
    int spaceUnits;
}
import static org.springframework.data.mongodb.core.aggregation.Aggregation.*;

TypedAggregation<Product> agg = newAggregation(Product.class,
    project("name", "netPrice")
        .and("netPrice").plus(1).as("netPricePlus1")
        .and("netPrice").minus(1).as("netPriceMinus1")
        .and("netPrice").multiply(1.19).as("grossPrice")
        .and("netPrice").divide(2).as("netPriceDiv2")
        .and("spaceUnits").mod(2).as("spaceUnitsMod2")
);

AggregationResults<Document> result = mongoTemplate.aggregate(agg, Document.class);
List<Document> resultList = result.getMappedResults();

请注意,我们从作为第一个参数传递给 Product 方法的 newAggregation 类派生出输入集合的名称。spring-doc.cadn.net.cn

聚合框架示例 5

此示例演示了在投影操作中使用源自SpEL表达式的简单算术运算。spring-doc.cadn.net.cn

class Product {
    String id;
    String name;
    double netPrice;
    int spaceUnits;
}
import static org.springframework.data.mongodb.core.aggregation.Aggregation.*;

TypedAggregation<Product> agg = newAggregation(Product.class,
    project("name", "netPrice")
        .andExpression("netPrice + 1").as("netPricePlus1")
        .andExpression("netPrice - 1").as("netPriceMinus1")
        .andExpression("netPrice / 2").as("netPriceDiv2")
        .andExpression("netPrice * 1.19").as("grossPrice")
        .andExpression("spaceUnits % 2").as("spaceUnitsMod2")
        .andExpression("(netPrice * 0.8  + 1.2) * 1.19").as("grossPriceIncludingDiscountAndCharge")

);

AggregationResults<Document> result = mongoTemplate.aggregate(agg, Document.class);
List<Document> resultList = result.getMappedResults();

聚合框架示例 6

此示例演示了在投影操作中使用源自 SpEL 表达式的复杂算术运算。spring-doc.cadn.net.cn

注意:addExpression 方法传入的附加参数可以根据其位置通过索引表达式进行引用。在此示例中,我们使用 [0] 引用参数数组中的第一个参数。当 SpEL 表达式被转换为 MongoDB 聚合框架表达式时,外部参数表达式将被替换为其对应的值。spring-doc.cadn.net.cn

class Product {
    String id;
    String name;
    double netPrice;
    int spaceUnits;
}
import static org.springframework.data.mongodb.core.aggregation.Aggregation.*;

double shippingCosts = 1.2;

TypedAggregation<Product> agg = newAggregation(Product.class,
    project("name", "netPrice")
        .andExpression("(netPrice * (1-discountRate)  + [0]) * (1+taxRate)", shippingCosts).as("salesPrice")
);

AggregationResults<Document> result = mongoTemplate.aggregate(agg, Document.class);
List<Document> resultList = result.getMappedResults();

请注意,我们也可以在 SpEL 表达式中引用文档的其他字段。spring-doc.cadn.net.cn

聚合框架示例 7

此示例使用了条件投影。它源自$cond 参考文档spring-doc.cadn.net.cn

public class InventoryItem {

  @Id int id;
  String item;
  String description;
  int qty;
}

public class InventoryItemProjection {

  @Id int id;
  String item;
  String description;
  int qty;
  int discount
}
import static org.springframework.data.mongodb.core.aggregation.Aggregation.*;

TypedAggregation<InventoryItem> agg = newAggregation(InventoryItem.class,
  project("item").and("discount")
    .applyCondition(ConditionalOperator.newBuilder().when(Criteria.where("qty").gte(250))
      .then(30)
      .otherwise(20))
    .and(ifNull("description", "Unspecified")).as("description")
);

AggregationResults<InventoryItemProjection> result = mongoTemplate.aggregate(agg, "inventory", InventoryItemProjection.class);
List<InventoryItemProjection> stateStatsList = result.getMappedResults();

此单步聚合操作对 inventory 集合使用了投影操作。我们通过条件操作为所有数量(discount)大于或等于 qty 的库存项投影出 250 字段。此外,还对 description 字段执行了第二个条件投影操作:对于所有未包含 Unspecified 字段或 description 值为 null 的项目,统一应用 8 描述。spring-doc.cadn.net.cn

从 MongoDB 3.6 开始,可以使用条件表达式从投影中排除字段。spring-doc.cadn.net.cn

示例7. 条件聚合投影
TypedAggregation<Book> agg = Aggregation.newAggregation(Book.class,
  project("title")
    .and(ConditionalOperators.when(ComparisonOperators.valueOf("author.middle")     (1)
        .equalToValue(""))                                                          (2)
        .then("$$REMOVE")                                                           (3)
        .otherwiseValueOf("author.middle")                                          (4)
    )
	.as("author.middle"));
1 如果字段 author.middle 的值
2 不包含值,
3 然后使用 $$REMOVE 来排除该字段。
4 否则,添加字段值 author.middle