跳到主要内容

聚合框架支持

DeepSeek V3 中英对照 Aggregation Framework Support

Spring Data MongoDB 提供了对 MongoDB 2.2 版本引入的聚合框架的支持。

更多信息,请参阅聚合框架及其他 MongoDB 数据聚合工具的完整参考文档

基本概念

Spring Data MongoDB 中的聚合框架支持基于以下关键抽象:AggregationAggregationResults

  • Aggregation

    Aggregation 表示 MongoDB 的 aggregate 操作,并持有聚合管道指令的描述。聚合操作是通过调用 Aggregation 类的 newAggregation(…) 静态工厂方法创建的,该方法接受一个 AggregateOperation 列表和一个可选的输入类作为参数。

    实际的聚合操作由 MongoTemplateaggregate 方法执行,该方法将所需的输出类作为参数。

  • TypedAggregation

    TypedAggregationAggregation 类似,持有聚合管道的指令以及对输入类型的引用,该引用用于将领域属性映射到实际的文档字段。

    在运行时,字段引用会根据给定的输入类型进行检查,同时考虑潜在的 @Field 注解。

在 3.2 版本中,引用不存在的属性不再会引发错误。要恢复之前的行为,请使用 AggregationOptionsstrictMapping 选项。

  • AggregationDefinition

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

  • AggregationResults

    AggregationResults 是聚合操作结果的容器。它提供了对原始聚合结果的访问,以 Document 的形式提供映射对象以及有关聚合的其他信息。

    以下列表展示了使用 Spring Data MongoDB 支持 MongoDB 聚合框架的典型示例:

    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();
    java

请注意,如果你将输入类作为 newAggregation 方法的第一个参数提供,MongoTemplate 会从该类中推导出输入集合的名称。否则,如果你没有指定输入类,则必须显式提供输入集合的名称。如果同时提供了输入类和输入集合,后者将优先使用。

支持的聚合操作与阶段

MongoDB 聚合框架提供了以下类型的聚合阶段和操作:

  • addFields - AddFieldsOperation(添加字段操作)

  • bucket / bucketAuto - BucketOperation / BucketAutoOperation(分桶操作 / 自动分桶操作)

  • count - CountOperation(计数操作)

  • densify - DensifyOperation(密集化操作)

  • facet - FacetOperation(分面操作)

  • geoNear - GeoNearOperation(地理邻近操作)

  • graphLookup - GraphLookupOperation(图查找操作)

  • group - GroupOperation(分组操作)

  • limit - LimitOperation(限制操作)

  • lookup - LookupOperation(查找操作)

  • match - MatchOperation(匹配操作)

  • merge - MergeOperation(合并操作)

  • project - ProjectionOperation(投影操作)

  • redact - RedactOperation(编辑操作)

  • replaceRoot - ReplaceRootOperation(替换根操作)

  • sample - SampleOperation(采样操作)

  • set - SetOperation(设置操作)

  • setWindowFields - SetWindowFieldsOperation(设置窗口字段操作)

  • skip - SkipOperation(跳过操作)

  • sort / sortByCount - SortOperation / SortByCountOperation(排序操作 / 按计数排序操作)

  • unionWith - UnionWithOperation(联合操作)

  • unset - UnsetOperation(取消设置操作)

  • unwind - UnwindOperation(展开操作)

提示

不支持的聚合阶段(例如 MongoDB Atlas 的 $search)可以通过实现 AggregationOperation 来提供。Aggregation.stage 是通过提供其 JSON 或 Bson 表示来注册管道阶段的快捷方式。

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

在撰写本文时,我们在 Spring Data MongoDB 中提供了对以下聚合操作符的支持:

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

|
|
| 集合聚合操作符 | setEquals, setIntersection, setUnion, setDifference, setIsSubset, anyElementTrue, allElementsTrue |
| 分组/累加器聚合操作符 | addToSet, bottom, bottomN, covariancePop, covarianceSamp, expMovingAvg, first, firstN, last, lastN max, maxN, min, minN, avg, push, sum, top, topN, count (*), median, percentile, stdDevPop, stdDevSamp |
| 算术聚合操作符 | 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, trunc |
| 字符串聚合操作符 | concat, substr, toLower, toUpper, strcasecmp, indexOfBytes, indexOfCP, regexFind, regexFindAll, regexMatch, replaceAll, replaceOne, split`, strLenBytes, strLenCP, substrCP, trim, ltrim, rtim |
| 比较聚合操作符 | eq (* via is), gt, gte, lt, lte, ne |
| 数组聚合操作符 | arrayElementAt, arrayToObject, concatArrays, filter, first, in, indexOfArray, isArray, last, range`, reverseArray, reduce, size, sortArray, slice, zip |
| 字面量操作符 | literal |
| 日期聚合操作符 | dateSubstract, dateTrunc, dayOfYear, dayOfMonth, dayOfWeek, year, month, week, hour, minute, second, millisecond, dateAdd, dateDiff, dateToString, dateFromString, dateFromParts, dateToParts, isoDayOfWeek, isoWeek, isoWeekYear, tsIncrement, tsSecond |
| 变量操作符 | map |
| 条件聚合操作符 | cond, ifNull, switch |
| 类型聚合操作符 | type |
| 转换聚合操作符 | convert, degreesToRadians, toBool, toDate, toDecimal, toDouble, toInt, toLong, toObjectId, toString |
| 对象聚合操作符 | objectToArray, mergeObjects, getField, setField |
| 脚本聚合操作符 | function, accumulator |

* 该操作由 Spring Data MongoDB 映射或添加。

请注意,Spring Data MongoDB 目前不支持此处未列出的聚合操作。比较聚合运算符以 Criteria 表达式形式表示。

投影表达式

投影表达式用于定义特定聚合步骤的输出字段。投影表达式可以通过 Aggregation 类的 project 方法来定义,可以通过传递一个 String 对象的列表或一个聚合框架的 Fields 对象来实现。投影可以通过使用 and(String) 方法使用流式 API 进行扩展,或者通过使用 as(String) 方法进行别名设置。需要注意的是,你也可以使用聚合框架的 Fields.field 静态工厂方法来定义带有别名的字段,然后可以使用它来构造一个新的 Fields 实例。在后续聚合阶段中对投影字段的引用仅对包含字段的字段名或其别名(包括新定义的字段及其别名)有效。未包含在投影中的字段不能在后续聚合阶段中引用。以下列表展示了投影表达式的示例:

示例 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")
java

示例 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")
java

更多关于项目操作的示例可以在 AggregationTests 类中找到。请注意,关于投影表达式的更多详细信息可以在 MongoDB 聚合框架参考文档 的相应部分中找到。

分面分类

自 3.4 版本起,MongoDB 通过使用聚合框架支持分面分类。分面分类使用语义类别(无论是通用的还是特定主题的),这些类别组合在一起以创建完整的分类条目。通过聚合管道的文档被分类到不同的桶中。多面分类允许在同一组输入文档上进行各种聚合操作,而无需多次检索输入文档。

分桶操作根据指定的表达式和分桶边界将传入的文档分类为不同的组,这些组被称为分桶。分桶操作需要一个分组字段或分组表达式。你可以通过 Aggregate 类的 bucket()bucketAuto() 方法来定义它们。BucketOperationBucketAutoOperation 可以根据聚合表达式为输入文档暴露累积值。你可以通过使用 with…() 方法和 andOutput(String) 方法,利用流畅的 API 扩展分桶操作,添加额外的参数。你可以使用 as(String) 方法为操作设置别名。每个分桶在输出中都表示为一个文档。

BucketOperation 使用一组定义好的边界将传入的文档分组到这些类别中。边界需要是有序的。以下是一些 bucket 操作的示例:

示例 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");
java

BucketAutoOperation 用于确定边界,以便将文档均匀分布到指定数量的桶中。BucketAutoOperation 可以选择性地接受一个粒度值,该值指定要使用的优先数系列,以确保计算出的边界边缘落在优先的整数或 10 的幂次方上。以下列表展示了一些桶操作的示例:

示例 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");
java

要在桶中创建输出字段,桶操作可以通过 andOutput() 使用 AggregationExpression,并通过 andOutputExpression() 使用 SpEL 表达式

多面聚合

多个聚合管道可以用于创建多面聚合(multi-faceted aggregations),这些聚合能够在一个单一的聚合阶段中从多个维度(或方面)对数据进行特征化。多面聚合提供了多个过滤器和分类,以指导数据的浏览和分析。分面(faceting)的一个常见实现方式是,许多在线零售商通过在产品价格、制造商、尺寸等因素上应用过滤器,来提供缩小搜索结果的方式。

你可以通过使用 Aggregation 类的 facet() 方法来定义一个 FacetOperation。你可以使用 and() 方法通过多个聚合管道来自定义它。每个子管道在输出文档中都有自己的字段,其结果存储为一个文档数组。

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

示例 5:切面操作示例

// 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"))
java

请注意,有关 facet 操作的更多详细信息可以在 MongoDB 聚合框架参考文档的 $facet 部分 中找到。

按计数排序

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

示例 6. 按计数排序的示例

// generates { $sortByCount: "$country" }
sortByCount("country");
java

按计数排序操作等价于以下 BSON(二进制 JSON):

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

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

我们通过 ProjectionOperationBucketOperation 类的 andExpression 方法支持在投影表达式中使用 SpEL 表达式。此功能允许您将所需的表达式定义为 SpEL 表达式。在运行查询时,SpEL 表达式会被转换为相应的 MongoDB 投影表达式部分。这种安排使得表达复杂计算变得更加容易。

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

考虑以下 SpEL 表达式:

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

前面的表达式被转换为以下投影表达式部分:

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

你可以在 聚合框架示例 5聚合框架示例 6 中查看更多上下文中的示例。你可以在 SpelExpressionTransformerUnitTests 中找到更多支持的 SpEL 表达式构造的使用示例。

支持的 SpEL 转换

SpEL 表达式Mongo 表达式部分
a == b{ eq:\[eq : \[a, $b] }
a != b{ ne:\[ne : \[a , $b] }
a > b{ gt:\[gt : \[a, $b] }
a >= b{ gte:\[gte : \[a, $b] }
a < b{ lt:\[lt : \[a, $b] }
a ⇐ b{ lte:\[lte : \[a, $b] }
a + b{ add:\[add : \[a, $b] }
a - b{ subtract:\[subtract : \[a, $b] }
a * b{ multiply:\[multiply : \[a, $b] }
a / b{ divide:\[divide : \[a, $b] }
a^b{ pow:\[pow : \[a, $b] }
a % b{ mod:\[mod : \[a, $b] }
a && b{ and:\[and : \[a, $b] }
ab{ or:\[or : \[a, $b] }
!a{ not:\[not : \[a] }

除了上表中展示的转换之外,你还可以使用标准的 SpEL 操作,例如 new 来(例如)创建数组并通过其名称引用表达式(后面跟着要使用的参数,放在括号中)。以下示例展示了如何以这种方式创建一个数组:

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

聚合框架示例

本节中的示例演示了如何结合 Spring Data MongoDB 使用 MongoDB 聚合框架的模式。

聚合框架示例 1

在这个入门示例中,我们希望聚合一个标签列表,以从 MongoDB 集合(称为 tags)中获取特定标签的出现次数,并按出现次数降序排序。此示例演示了分组、排序、投影(选择)和展开(结果拆分)的用法。

class TagCount {
String tag;
int n;
}
java
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();
java

前面的代码清单使用了以下算法:

  1. 使用 newAggregation 静态工厂方法创建一个新的聚合操作,并向其传递一组聚合操作列表。这些聚合操作定义了我们的 Aggregation 的聚合管道。

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

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

  4. 使用 group 操作为每个 tags 值定义一个分组,并为该分组聚合出现次数(通过使用 count 聚合操作符,并将结果收集到一个名为 n 的新字段中)。

  5. 选择 n 字段,并为前一个分组操作生成的 ID 字段创建一个别名(因此调用 previousOperation()),命名为 tag

  6. 使用 sort 操作按标签的出现次数对结果列表进行降序排序。

  7. MongoTemplate 上调用 aggregate 方法,让 MongoDB 执行实际的聚合操作,并将创建的 Aggregation 作为参数传递。

请注意,输入集合显式指定为 aggregate 方法的 tags 参数。如果未显式指定输入集合的名称,则会从作为 newAggregation 方法的第一个参数传递的输入类中派生。

聚合框架示例 2

本示例基于 MongoDB 聚合框架文档 中的 按州划分的最大和最小城市 示例。我们添加了额外的排序,以便在不同版本的 MongoDB 中生成稳定的结果。在这里,我们希望使用聚合框架返回每个州中按人口划分的最小和最大城市。该示例演示了分组、排序和投影(选择)。

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;
}
java
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);
java

请注意,ZipInfo 类映射了给定输入集合的结构。而 ZipInfoStats 类则定义了所需输出格式的结构。

前面的列表使用了以下算法:

  1. 使用 group 操作从输入集合中定义一个组。分组标准是 statecity 字段的组合,这构成了组的 ID 结构。我们通过使用 sum 操作符从分组元素中聚合 population 属性的值,并将结果保存在 pop 字段中。

  2. 使用 sort 操作按 popstatecity 字段以升序对中间结果进行排序,使得结果中最小的城市位于顶部,最大的城市位于底部。请注意,对 statecity 的排序是隐式地针对组 ID 字段执行的(Spring Data MongoDB 已处理)。

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

  4. 从上一个 group 操作中选择 state 字段。请注意,state 再次隐式地引用了一个组 ID 字段。由于我们不希望出现隐式生成的 ID,我们通过使用 and(previousOperation()).exclude() 来排除上一个操作中的 ID。由于我们希望在输出类中填充嵌套的 City 结构,因此我们通过使用嵌套方法来发出适当的子文档。

  5. sort 操作中按州名称以升序对结果列表 StateStats 进行排序。

请注意,我们从作为 newAggregation 方法的第一个参数传递的 ZipInfo 类中派生出输入集合的名称。

聚合框架示例 3

这个示例基于 MongoDB 聚合框架文档中的 States with Populations Over 10 Million 示例。我们添加了额外的排序以确保在不同版本的 MongoDB 中生成稳定的结果。在这里,我们希望使用聚合框架返回所有人口超过 1000 万的州。此示例展示了分组、排序和匹配(过滤)操作。

class StateStats {
@Id String id;
String state;
@Field("totalPop") int totalPopulation;
}
java
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();
java

前面的代码清单使用了以下算法:

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

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

  3. 使用接受 Criteria 查询作为参数的 match 操作对中间结果进行过滤。

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

聚合框架示例 4

这个示例演示了在投影操作中使用简单的算术运算。

class Product {
String id;
String name;
double netPrice;
int spaceUnits;
}
java
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();
java

请注意,我们从作为 newAggregation 方法的第一个参数传递的 Product 类中推导出输入集合的名称。

聚合框架示例 5

这个示例演示了在投影操作中使用基于 SpEL 表达式的简单算术运算。

class Product {
String id;
String name;
double netPrice;
int spaceUnits;
}
java
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();
java

聚合框架示例 6

这个示例演示了在投影操作中使用从 SpEL 表达式派生的复杂算术运算。

注意:传递给 addExpression 方法的附加参数可以根据其位置通过索引表达式进行引用。在这个例子中,我们使用 [0] 引用了参数数组中的第一个参数。当 SpEL 表达式转换为 MongoDB 聚合框架表达式时,外部参数表达式会被替换为它们各自的值。

class Product {
String id;
String name;
double netPrice;
int spaceUnits;
}
java
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();
java

注意,我们也可以在 SpEL 表达式中引用文档的其他字段。

聚合框架示例 7

这个示例使用了条件投影。它源自于 $cond 参考文档

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
}
java
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();
java

这种一步聚合操作使用了 inventory 集合的投影操作。我们通过对所有 qty 大于等于 250 的库存项使用条件操作来投影 discount 字段。然后对 description 字段执行第二个条件投影。我们将 Unspecified 描述应用于所有没有 description 字段或 descriptionnull 的项。

从 MongoDB 3.6 开始,可以通过使用条件表达式在投影中排除字段。

示例 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"));
java
  • 如果字段 author.middle 的值

  • 不包含值,

  • 则使用 $$REMOVE 来排除该字段。

  • 否则,添加 author.middle 的字段值。