对于最新的稳定版本,请使用 Spring Data MongoDB 4.4.0! |
聚合框架支持
Spring Data MongoDB 为版本 2.2 中引入 MongoDB 的聚合框架提供支持。
有关更多信息,请参阅聚合框架和 MongoDB 的其他数据聚合工具的完整参考文档。
基本概念
Spring Data MongoDB 中的聚合框架支持基于以下关键抽象:Aggregation
和 AggregationResults
。
-
Aggregation
An 表示 MongoDB 操作,并保存聚合管道指令的描述。通过调用类的相应静态工厂方法创建聚合,该方法采用 list of 和可选的 input 类。
Aggregation
aggregate
newAggregation(…)
Aggregation
AggregateOperation
实际的聚合操作由 的方法运行,该方法将所需的输出类作为参数。
aggregate
MongoTemplate
-
TypedAggregation
A 与 一样,保存聚合管道的指令和对 input 类型的引用,用于将域属性映射到实际文档字段。
TypedAggregation
Aggregation
在运行时,会根据给定的 input 类型检查字段引用,同时考虑潜在的注释。
@Field
在 3.2 中更改:引用不存在的属性不再引发错误。要恢复之前的行为,请使用 .strictMapping
AggregationOptions
-
AggregationDefinition
An 表示 MongoDB 聚合管道操作,并描述应在此聚合步骤中执行的处理。尽管您可以手动创建一个 ,但我们建议使用类提供的静态工厂方法来构造一个 .
AggregationDefinition
AggregationDefinition
Aggregate
AggregateOperation
-
AggregationResults
AggregationResults
是聚合操作结果的容器。它以 a 的形式提供对原始聚合结果的访问,以访问映射的对象以及有关聚合的其他信息。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();
请注意,如果提供输入类作为方法的第一个参数,则会从此类派生输入集合的名称。否则,如果未指定 input 类,则必须显式提供 input 集合的名称。如果同时提供了 input 类和 input 集合,则后者优先。newAggregation
MongoTemplate
支持的聚合操作和阶段
MongoDB 聚合框架提供以下类型的聚合阶段和操作:
-
addFields -
AddFieldsOperation
-
桶 / 桶Auto -
BucketOperation
/BucketAutoOperation
-
计数-
CountOperation
-
致密化 -
DensifyOperation
-
方面-
FacetOperation
-
地理近 -
GeoNearOperation
-
graphLookup -
GraphLookupOperation
-
群-
GroupOperation
-
限制-
LimitOperation
-
查找-
LookupOperation
-
火柴-
MatchOperation
-
合并-
MergeOperation
-
项目-
ProjectionOperation
-
编辑-
RedactOperation
-
replaceRoot -
ReplaceRootOperation
-
样本-
SampleOperation
-
设置-
SetOperation
-
setWindowFields -
SetWindowFieldsOperation
-
跳-
SkipOperation
-
排序 / sortByCount -
SortOperation
/SortByCountOperation
-
unionWith -
UnionWithOperation
-
未凝固的-
UnsetOperation
-
放松-
UnwindOperation
不支持的聚合阶段(如 $search for MongoDB Atlas)可以通过实施 . 是通过提供管道阶段的 JSON 或表示形式来注册管道阶段的快捷方式。
|
在撰写本文时,我们为 Spring Data MongoDB 中的以下聚合运算符提供支持:
设置聚合运算符 |
|
Group/Accumulator 聚合运算符 |
|
算术聚合运算符 |
|
字符串聚合运算符 |
|
比较聚合运算符 |
|
数组聚合运算符 |
|
文本运算符 |
|
日期聚合运算符 |
|
变量运算符 |
|
条件聚合运算符 |
|
类型聚合运算符 |
|
转换聚合运算符 |
|
对象聚合运算符 |
|
脚本聚合运算符 |
|
* 该操作由 Spring Data MongoDB 映射或添加。
请注意,Spring Data MongoDB 当前不支持此处未列出的聚合操作。比较聚合运算符表示为表达式。Criteria
投影表达式
投影表达式用于定义作为特定聚合步骤结果的字段。可以通过类的方法定义投影表达式,方法是传递对象列表或聚合框架对象。通过使用该方法,可以通过 Fluent API 使用其他字段扩展投影,并使用该方法为投影添加别名。
请注意,您还可以使用聚合框架的 static factory 方法定义具有别名的字段,然后可以使用该方法构造新实例。在后续聚合阶段中对投影字段的引用仅对包含的字段或其别名(包括新定义的字段及其别名)的字段名称有效。投影中未包含的字段不能在以后的聚合阶段中引用。以下清单显示了 projection 表达式的示例:project
Aggregation
String
Fields
and(String)
as(String)
Fields.field
Fields
// 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")
// 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")
可以在该类中找到更多项目操作示例。请注意,有关投影表达式的更多详细信息,请参阅 MongoDB 聚合框架参考文档的相应部分。AggregationTests
分面分类
从版本 3.4 开始,MongoDB 支持使用聚合框架进行分面分类。分面分类使用语义类别(常规或特定于主题),这些类别组合在一起以创建完整的分类条目。流经聚合管道的文档被分类到存储桶中。多方面分类支持对同一组输入文档进行各种聚合,而无需多次检索输入文档。
桶
存储桶操作根据指定的表达式和存储桶边界将传入文档分类为多个组,称为存储桶。存储桶操作需要分组字段或分组表达式。您可以使用类的 and 方法来定义它们。 并且可以根据输入文档的聚合表达式公开累积。您可以通过 Fluent API 使用 methods 和 method 扩展具有其他参数的存储桶操作。您可以使用 该方法为操作设置别名。每个存储桶在输出中都表示为一个文档。bucket()
bucketAuto()
Aggregate
BucketOperation
BucketAutoOperation
with…()
andOutput(String)
as(String)
BucketOperation
采用一组定义的边界,将传入文档分组到这些类别中。边界需要排序。以下清单显示了存储桶操作的一些示例:
// 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
确定边界,以尝试将文档均匀分配到指定数量的存储桶中。 (可选)采用一个粒度值,该值指定用于确保计算的边界边以首选整数或 10 的幂结束。以下清单显示了存储桶操作的示例:BucketAutoOperation
// 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");
要在存储桶中创建输出字段,存储桶操作可以使用 through 和 SPEL 表达式 through。AggregationExpression
andOutput()
andOutputExpression()
请注意,有关存储桶表达式的更多详细信息,请参阅 MongoDB 聚合框架参考文档的 $bucket
部分和 $bucketAuto
部分。
多方位聚合
多个聚合管道可用于创建多方面聚合,以描述单个聚合阶段中多个维度(或分面)的数据特征。多方面聚合提供了多个筛选条件和分类,以指导数据浏览和分析。分面的常见实现是,许多在线零售商通过对产品价格、制造商、大小和其他因素应用筛选器来缩小搜索结果的范围。
您可以使用类的方法定义 a。您可以使用该方法使用多个聚合管道对其进行自定义。每个子管道在输出文档中都有自己的字段,其结果作为文档数组存储。FacetOperation
facet()
Aggregation
and()
子管道可以在分组之前投影和筛选输入文档。常见用例包括在分类之前提取日期部分或计算。以下清单显示了 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 聚合框架参考文档的 $facet
部分。
按计数排序
按计数排序操作根据指定表达式的值对传入文档进行分组,计算每个不同组中的文档计数,并按计数对结果进行排序。它提供了一个方便的快捷方式,用于在使用分面分类时应用排序。按计数排序操作需要分组字段或分组表达式。以下清单显示了一个按计数排序的示例:
// generates { $sortByCount: "$country" }
sortByCount("country");
按计数排序操作等效于以下 BSON(二进制 JSON):
{ $group: { _id: <expression>, count: { $sum: 1 } } }, { $sort: { count: -1 } }
投影表达式中的 Spring 表达式支持
我们支持通过 and 类的方法在投影表达式中使用 SPEL 表达式。此功能允许您将所需的表达式定义为 SPEL 表达式。在运行查询时,SpEL 表达式将转换为相应的 MongoDB 投影表达式部分。这种安排使表达复杂的计算变得更加容易。andExpression
ProjectionOperation
BucketOperation
使用 SPEL 表达式进行复杂计算
考虑以下 SPEL 表达式:
1 + (q + 1) / (q - 1)
上述表达式将转换为以下 projection 表达式部分:
{ "$add" : [ 1, {
"$divide" : [ {
"$add":["$q", 1]}, {
"$subtract":[ "$q", 1]}
]
}]}
您可以在 Aggregation Framework 示例 5 和 Aggregation Framework 示例 6 中查看更多上下文示例。
您可以在 中找到受支持的 SpEL 表达式结构的更多使用示例。SpelExpressionTransformerUnitTests
支持的 SPEL 转换
SPEL 表达式 | Mongo 表达式部分 |
---|---|
a == b |
{ $eq : [$a, $b] } |
a != b |
{ $ne : [$a , $b] } |
a > b |
{ $gt : [$a, $b] } |
a >= b |
{ $gte : [$a, $b] } |
a < b |
{ $lt : [$a, $b] } |
a ⇐ b |
{ $lte : [$a, $b] } |
a + b |
{ $add : [$a, $b] } |
一 - 乙 |
{ $subtract : [$a, $b] } |
a * b |
{ $multiply : [$a, $b] } |
一个 / b |
{ $divide : [$a, $b] } |
a^b |
{ $pow : [$a, $b] } |
a % b |
{ $mod : [$a, $b] } |
a & b |
{ $and : [$a, $b] } |
一个 ||b |
{ $or : [$a, $b] } |
!一个 |
{ $not : [$a] } |
除了上表所示的转换之外,您还可以使用标准 SPEL 操作,例如(例如)通过其名称创建数组和引用表达式(后跟要在括号中使用的参数)。下面的示例演示如何以这种方式创建数组:new
// { $setEquals : [$a, [5, 8, 13] ] }
.andExpression("setEquals(a, new int[]{5, 8, 13})");
聚合框架示例
本节中的示例演示了 MongoDB 聚合框架与 Spring Data 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();
前面的清单使用以下算法:
-
使用 static 工厂方法创建一个新的聚合,我们将聚合操作列表传递给该方法。这些聚合操作定义了 .
newAggregation
Aggregation
-
使用该操作从输入集合中选择字段(字符串数组)。
project
tags
-
使用该操作为数组中的每个标签生成一个新文档。
unwind
tags
-
使用该操作为聚合发生次数的每个值定义一个组(通过使用聚合运算符并将结果收集到名为 的新字段中)。
group
tags
count
n
-
选择字段,并为从上一个组操作(因此调用 )生成的 ID 字段创建一个别名,其名称为 。
n
previousOperation()
tag
-
使用该操作按标记的出现次数降序对生成的标记列表进行排序。
sort
-
调用 on 方法,让 MongoDB 执行实际的聚合操作,并将 created 作为参数。
aggregate
MongoTemplate
Aggregation
请注意,输入集合被显式指定为 Method 的参数。如果未显式指定输入集合的名称,则它是从作为第一个参数传递给方法的输入类派生的。tags
aggregate
newAggreation
聚合框架示例 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);
请注意,该类映射给定 input-collection 的结构。该类以所需的输出格式定义结构。ZipInfo
ZipInfoStats
前面的清单使用以下算法:
-
使用该操作从 input-collection 中定义一个组。分组标准是 和 字段的组合,它构成了组的 ID 结构。我们使用运算符从分组的元素中聚合属性的值,并将结果保存在字段中。
group
state
city
population
sum
pop
-
使用该操作按 、 和 字段对中间结果进行升序排序,以便最小的城市位于结果的顶部,最大的城市位于结果的底部。请注意,对 and 的排序是针对组 ID 字段(Spring Data MongoDB 处理的)隐式执行的。
sort
pop
state
city
state
city
-
再次使用操作可按 对中间结果进行分组。请注意,再次隐式引用组 ID 字段。我们在操作中分别通过调用 the 和 运算符来选择最大和最小城市的名称和人口计数。
group
state
state
last(…)
first(…)
project
-
从上一个操作中选择字段。请注意,再次隐式引用组 ID 字段。由于我们不希望显示隐式生成的 ID,因此我们使用 .因为我们想要在输出类中填充嵌套结构,所以我们必须使用 nested 方法发出适当的子文档。
state
group
state
and(previousOperation()).exclude()
City
-
在操作中按状态名称对结果列表 of 进行升序排序。
StateStats
sort
请注意,我们从作为第一个参数传递给方法的类中派生输入集合的名称。ZipInfo
newAggregation
聚合框架示例 3
此示例基于 MongoDB 聚合框架文档中的人口超过 1000 万的州示例。我们添加了额外的排序,以便在不同的 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"
-
除了按字段升序排序外,还按上一个组操作的 id-reference 对中间结果进行排序。
"totalPop"
-
使用接受查询作为参数的操作筛选中间结果。
match
Criteria
请注意,我们从作为第一个参数传递给方法的类中派生输入集合的名称。ZipInfo
newAggregation
聚合框架示例 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();
请注意,我们从作为第一个参数传递给方法的类中派生输入集合的名称。Product
newAggregation
聚合框架示例 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 表达式派生的复杂算术运算。
注意:传递给该方法的其他参数可以根据其位置使用索引器表达式进行引用。在此示例中,我们使用 .当 SPEL 表达式转换为 MongoDB 聚合框架表达式时,外部参数表达式将替换为它们各自的值。addExpression
[0]
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
discount
qty
250
description
Unspecified
description
null
从 MongoDB 3.6 开始,可以使用条件表达式从投影中排除字段。
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 |