聚合框架支持
Spring Data MongoDB 提供了对 MongoDB 2.2 版本引入的聚合框架的支持。
更多信息,请参阅聚合框架及其他 MongoDB 数据聚合工具的完整参考文档。
基本概念
Spring Data MongoDB 中的聚合框架支持基于以下关键抽象:Aggregation 和 AggregationResults。
-
Aggregation
Aggregation
表示 MongoDB 的aggregate
操作,并持有聚合管道指令的描述。聚合操作是通过调用Aggregation
类的newAggregation(…)
静态工厂方法创建的,该方法接受一个AggregateOperation
列表和一个可选的输入类作为参数。实际的聚合操作由
MongoTemplate
的aggregate
方法执行,该方法将所需的输出类作为参数。 -
TypedAggregation
TypedAggregation
与Aggregation
类似,持有聚合管道的指令以及对输入类型的引用,该引用用于将领域属性映射到实际的文档字段。在运行时,字段引用会根据给定的输入类型进行检查,同时考虑潜在的
@Field
注解。
在 3.2 版本中,引用不存在的属性不再会引发错误。要恢复之前的行为,请使用 AggregationOptions
的 strictMapping
选项。
-
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();
请注意,如果你将输入类作为 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
}
}
}
""");
在撰写本文时,我们在 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")
示例 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 聚合框架参考文档 的相应部分中找到。
分面分类
自 3.4 版本起,MongoDB 通过使用聚合框架支持分面分类。分面分类使用语义类别(无论是通用的还是特定主题的),这些类别组合在一起以创建完整的分类条目。通过聚合管道的文档被分类到不同的桶中。多面分类允许在同一组输入文档上进行各种聚合操作,而无需多次检索输入文档。
桶
分桶操作根据指定的表达式和分桶边界将传入的文档分类为不同的组,这些组被称为分桶。分桶操作需要一个分组字段或分组表达式。你可以通过 Aggregate
类的 bucket()
和 bucketAuto()
方法来定义它们。BucketOperation
和 BucketAutoOperation
可以根据聚合表达式为输入文档暴露累积值。你可以通过使用 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");
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");
要在桶中创建输出字段,桶操作可以通过 andOutput()
使用 AggregationExpression
,并通过 andOutputExpression()
使用 SpEL 表达式。
请注意,有关桶表达式的更多详细信息可以在 MongoDB 聚合框架参考文档的 bucketAuto 部分 中找到。
多面聚合
多个聚合管道可以用于创建多面聚合(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"))
请注意,有关 facet 操作的更多详细信息可以在 MongoDB 聚合框架参考文档的 $facet 部分 中找到。
按计数排序
按计数排序操作会根据指定表达式的值对传入的文档进行分组,计算每个不同组中的文档数量,并按计数对结果进行排序。在使用 Faceted Classification 时,它提供了一种便捷的快捷方式来应用排序。按计数排序操作需要一个分组字段或分组表达式。以下示例展示了一个按计数排序的例子:
示例 6. 按计数排序的示例
// generates { $sortByCount: "$country" }
sortByCount("country");
按计数排序操作等价于以下 BSON(二进制 JSON):
{ $group: { _id: <expression>, count: { $sum: 1 } } },
{ $sort: { count: -1 } }
投影表达式中的 Spring 表达式支持
我们通过 ProjectionOperation
和 BucketOperation
类的 andExpression
方法支持在投影表达式中使用 SpEL 表达式。此功能允许您将所需的表达式定义为 SpEL 表达式。在运行查询时,SpEL 表达式会被转换为相应的 MongoDB 投影表达式部分。这种安排使得表达复杂计算变得更加容易。
使用 SpEL 表达式进行复杂计算
考虑以下 SpEL 表达式:
1 + (q + 1) / (q - 1)
前面的表达式被转换为以下投影表达式部分:
{ "$add" : [ 1, {
"$divide" : [ {
"$add":["$q", 1]}, {
"$subtract":[ "$q", 1]}
]
}]}
支持的 SpEL 转换
SpEL 表达式 | Mongo 表达式部分 | ||
---|---|---|---|
a == b | { a, $b] } | ||
a != b | { a , $b] } | ||
a > b | { a, $b] } | ||
a >= b | { a, $b] } | ||
a < b | { a, $b] } | ||
a ⇐ b | { a, $b] } | ||
a + b | { a, $b] } | ||
a - b | { a, $b] } | ||
a * b | { a, $b] } | ||
a / b | { a, $b] } | ||
a^b | { a, $b] } | ||
a % b | { a, $b] } | ||
a && b | { a, $b] } | ||
a | b | { a, $b] } | |
!a | { a] } |
除了上表中展示的转换之外,你还可以使用标准的 SpEL 操作,例如 new
来(例如)创建数组并通过其名称引用表达式(后面跟着要使用的参数,放在括号中)。以下示例展示了如何以这种方式创建一个数组:
// { $setEquals : [$a, [5, 8, 13] ] }
.andExpression("setEquals(a, new int[]{5, 8, 13})");
聚合框架示例
本节中的示例演示了如何结合 Spring Data MongoDB 使用 MongoDB 聚合框架的模式。
聚合框架示例 1
在这个入门示例中,我们希望聚合一个标签列表,以从 MongoDB 集合(称为 tags
)中获取特定标签的出现次数,并按出现次数降序排序。此示例演示了分组、排序、投影(选择)和展开(结果拆分)的用法。
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();
前面的代码清单使用了以下算法:
-
使用
newAggregation
静态工厂方法创建一个新的聚合操作,并向其传递一组聚合操作列表。这些聚合操作定义了我们的Aggregation
的聚合管道。 -
使用
project
操作从输入集合中选择tags
字段(该字段是一个字符串数组)。 -
使用
unwind
操作为tags
数组中的每个标签生成一个新文档。 -
使用
group
操作为每个tags
值定义一个分组,并为该分组聚合出现次数(通过使用count
聚合操作符,并将结果收集到一个名为n
的新字段中)。 -
选择
n
字段,并为前一个分组操作生成的 ID 字段创建一个别名(因此调用previousOperation()
),命名为tag
。 -
使用
sort
操作按标签的出现次数对结果列表进行降序排序。 -
在
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;
}
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
类则定义了所需输出格式的结构。
前面的列表使用了以下算法:
-
使用
group
操作从输入集合中定义一个组。分组标准是state
和city
字段的组合,这构成了组的 ID 结构。我们通过使用sum
操作符从分组元素中聚合population
属性的值,并将结果保存在pop
字段中。 -
使用
sort
操作按pop
、state
和city
字段以升序对中间结果进行排序,使得结果中最小的城市位于顶部,最大的城市位于底部。请注意,对state
和city
的排序是隐式地针对组 ID 字段执行的(Spring Data MongoDB 已处理)。 -
再次使用
group
操作按state
对中间结果进行分组。请注意,state
再次隐式地引用了一个组 ID 字段。我们在project
操作中分别通过调用last(…)
和first(…)
操作符来选择最大和最小城市的名称和人口数量。 -
从上一个
group
操作中选择state
字段。请注意,state
再次隐式地引用了一个组 ID 字段。由于我们不希望出现隐式生成的 ID,我们通过使用and(previousOperation()).exclude()
来排除上一个操作中的 ID。由于我们希望在输出类中填充嵌套的City
结构,因此我们通过使用嵌套方法来发出适当的子文档。 -
在
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;
}
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();
前面的代码清单使用了以下算法:
-
按
state
字段对输入集合进行分组,并计算population
字段的总和,将结果存储在新字段"totalPop"
中。 -
除了按
"totalPop"
字段升序排序外,还按前一个分组操作的 id 引用对中间结果进行排序。 -
使用接受
Criteria
查询作为参数的match
操作对中间结果进行过滤。
请注意,我们从作为第一个参数传递给 newAggregation
方法的 ZipInfo
类中派生出输入集合的名称。
聚合框架示例 4
这个示例演示了在投影操作中使用简单的算术运算。
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();
请注意,我们从作为 newAggregation
方法的第一个参数传递的 Product
类中推导出输入集合的名称。
聚合框架示例 5
这个示例演示了在投影操作中使用基于 SpEL 表达式的简单算术运算。
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 表达式派生的复杂算术运算。
注意:传递给 addExpression
方法的附加参数可以根据其位置通过索引表达式进行引用。在这个例子中,我们使用 [0]
引用了参数数组中的第一个参数。当 SpEL 表达式转换为 MongoDB 聚合框架表达式时,外部参数表达式会被替换为它们各自的值。
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 表达式中引用文档的其他字段。
聚合框架示例 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
}
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
集合的投影操作。我们通过对所有 qty
大于等于 250
的库存项使用条件操作来投影 discount
字段。然后对 description
字段执行第二个条件投影。我们将 Unspecified
描述应用于所有没有 description
字段或 description
为 null
的项。
从 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"));
如果字段
author.middle
的值不包含值,
则使用 $$REMOVE 来排除该字段。
否则,添加
author.middle
的字段值。