原标题:Spring认证中国教育管理中心-Spring Data MongoDB教程七(内容来源:Spring中国教育管理中心) 11.10.脚本操作的MongoDB 4.2对被移除的支撑eval通过使用命令ScriptOperations。 MongoDB 允许通过直接发送脚本或调用存储的脚本在服务器上运行 JavaScript 函数。ScriptOperations可以通过访问MongoTemplate并提供基本的JavaScript使用抽象。以下示例显示了如何使用ScriptOperations该类: ScriptOperations scriptOps = template.scriptOps(); ExecutableMongoScript echoScript = new ExecutableMongoScript("function(x) { return x; }"); scriptOps.execute(echoScript, "directly execute script"); scriptOps.register(new NamedMongoScript("echo", echoScript)); scriptOps.call("echo", "execute script via name"); 直接运行脚本,无需在服务器端存储函数。 使用“echo”作为名称存储脚本。给定的名称标识脚本并允许稍后调用它。 使用提供的参数运行名为“echo”的脚本。 11.11.集团运营作为替代使用的map-reduce进行数据汇总,您可以使用group操作这感觉类似于使用SQL的group by查询的风格,所以它可以使用的map-reduce感觉更平易近人对比。使用 group 操作确实有一些限制,例如它在共享环境中不受支持,它返回单个 BSON 对象中的完整结果集,因此结果应该很小,少于 10,000 个键。 Spring 通过在 MongoOperations 上提供方法来提供与 MongoDB 的组操作的集成,以简化组操作的创建和运行。它可以将分组操作的结果转换为POJO,并且还集成了Spring的Resource抽象抽象。这将允许您将 JavaScript 文件放在文件系统、类路径、http 服务器或任何其他 Spring 资源实现上,然后通过简单的 URI 样式语法(例如“classpath:reduce.js;”)引用 JavaScript 资源。如果通常将文件中的 JavaScript 代码作为 Java 字符串嵌入到您的代码中更可取,那么在文件中外部化 JavaScript 代码。请注意,如果您愿意,您仍然可以将 JavaScript 代码作为 Java 字符串传递。 11.11.1.示例用法为了理解组操作是如何工作的,使用以下示例,这有点人为。有关更现实的示例,请参阅“MongoDB - 权威指南”一书。group_test_collection使用以下行创建的名为的集合。 { "_id" : ObjectId("4ec1d25d41421e2015da64f1"), "x" : 1 } { "_id" : ObjectId("4ec1d25d41421e2015da64f2"), "x" : 1 } { "_id" : ObjectId("4ec1d25d41421e2015da64f3"), "x" : 2 } { "_id" : ObjectId("4ec1d25d41421e2015da64f4"), "x" : 3 } { "_id" : ObjectId("4ec1d25d41421e2015da64f5"), "x" : 3 } { "_id" : ObjectId("4ec1d25d41421e2015da64f6"), "x" : 3 } 我们想按每行中唯一的字段进行分组,该x字段和聚合每个特定值x出现的次数。为此,我们需要创建一个初始文档,其中包含我们的 count 变量和一个 reduce 函数,每次遇到它时都会增加它。运行组操作的Java代码如下所示 GroupByResults<XObject> results = mongoTemplate.group("group_test_collection", GroupBy.key("x").initialDocument("{ count: 0 }").reduceFunction("function(doc, prev) { prev.count += 1 }"), XObject.class); 第一个参数是运行组操作的集合的名称,第二个参数是一个流利的 API,它通过一个GroupBy类指定组操作的属性。在这个例子中,我们只使用intialDocument和reduceFunction方法。您还可以指定键函数以及终结器作为 fluent API 的一部分。如果您有多个要分组的键,则可以传入逗号分隔的键列表。 group 操作的原始结果是一个 JSON 文档,看起来像这样 { "retval" : [ { "x" : 1.0 , "count" : 2.0} , { "x" : 2.0 , "count" : 1.0} , { "x" : 3.0 , "count" : 3.0} ] , "count" : 6.0 , "keys" : 3 , "ok" : 1.0} “retval”字段下的文档映射到 group 方法中的第三个参数,在本例中为 XObject,如下所示。 public class XObject { private float x; private float count; public float getX() { return x; } public void setX(float x) { this.x = x; } public float getCount() { return count; } public void setCount(float count) { this.count = count; } @Override public String toString() { return "XObject [x=" + x + " count = " + count + "]"; } } 您还可以Document通过调用类getRawResults上的方法来获取原始结果GroupByResults。 group 方法有一个额外的方法重载,MongoOperations它允许您指定一个Criteria对象来选择行的子集。下面显示了一个使用Criteria对象的示例,其中包含一些使用静态导入的语法糖,以及通过 Spring 资源字符串引用 key-function 和 reduce function javascript 文件的示例。 import static org.springframework.data.mongodb.core.mapreduce.GroupBy.keyFunction;import static org.springframework.data.mongodb.core.query.Criteria.where; GroupByResults<XObject> results = mongoTemplate.group(where("x").gt(0), "group_test_collection", keyFunction("classpath:keyFunction.js").initialDocument("{ count: 0 }").reduceFunction("classpath:groupReduce.js"), XObject.class); 11.12.聚合框架支持Spring Data MongoDB 为 2.2 版中引入到 MongoDB 的聚合框架提供支持。 有关更多信息,请参阅MongoDB 的聚合框架和其他数据聚合工具的完整参考文档。 11.12.1.基本概念在Spring数据MongoDB中的聚合框架的支持是基于以下关键抽象:Aggregation,AggregationDefinition,和AggregationResults。
在 3.2 中更改引用不存在的属性不再引发错误。要恢复以前的行为,请使用strictMapping选项AggregationOptions。
请注意,如果您提供输入类作为该newAggregation方法的第一个参数,MongoTemplate则从该类派生输入集合的名称。否则,如果未指定输入类,则必须明确提供输入集合的名称。如果同时提供输入类和输入集合,则后者优先。 11.12.2.支持的聚合操作MongoDB 聚合框架提供以下类型的聚合操作:
在撰写本文时,我们为 Spring Data MongoDB 中的以下聚合操作提供支持: * 操作由 Spring Data MongoDB 映射或添加。 请注意,Spring Data MongoDB 目前不支持此处未列出的聚合操作。比较聚合运算符表示为Criteria表达式。 11.12.3.投影表达式投影表达式用于定义作为特定聚合步骤结果的字段。可以通过类的project方法定义投影表达式Aggregation,通过传递String对象列表或聚合框架Fields对象。投影可以通过 fluent API 使用该and(String)方法扩展附加字段,并使用该方法别名as(String)。请注意,您还可以使用Fields.field聚合框架的静态工厂方法定义带有别名的字段,然后您可以使用它来构造一个新的Fields实例。后期聚合阶段对投影字段的引用仅对包含字段的字段名称或其别名(包括新定义的字段及其别名)有效。未包含在投影中的字段不能在后面的聚合阶段引用。以下清单显示了投影表达式的示例: 示例 99. 投影表达式示例 // 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") 示例 100. 使用投影和排序的多阶段聚合 // 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 workproject().and("firstname").as("name"), sort(ASC, "firstname") 更多项目操作示例可以在AggregationTests课程中找到。请注意,有关投影表达式的更多详细信息可以在 MongoDB 聚合框架参考文档的相应部分中找到。 11.12.4.分面分类从版本 3.4 开始,MongoDB 通过使用聚合框架支持分面分类。分面分类使用组合起来创建完整分类条目的语义类别(一般的或特定于主题的)。流经聚合管道的文档被分类到桶中。多面分类可以对同一组输入文档进行各种聚合,而无需多次检索输入文档。 桶存储桶操作根据指定的表达式和存储桶边界将传入文档分类为多个组,称为存储桶。桶操作需要一个分组字段或一个分组表达式。您可以使用类的bucket()和bucketAuto()方法定义它们Aggregate。BucketOperation并且BucketAutoOperation可以基于输入文档的聚合表达式公开累积。您可以使用with…()方法和andOutput(String)方法通过流畅的 API 使用附加参数扩展存储桶操作。您可以使用as(String)方法为操作设置别名。每个存储桶在输出中表示为一个文档。 BucketOperation使用一组定义的边界将传入的文档分组到这些类别中。边界需要排序。以下清单显示了存储桶操作的一些示例: 示例 101. 存储桶操作示例 // 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 的幂结束。以下清单显示了存储桶操作的示例: 示例 102. 存储桶操作示例 // 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"); 要在存储桶中创建输出字段,存储桶操作可以使用 请注意,可以在 MongoDB 聚合框架参考文档的$bucket一节和 $bucketAuto一节中找到有关存储桶表达式的更多详细信息。 多面聚合多个聚合管道可用于创建多方面聚合,在单个聚合阶段内表征跨多个维度(或方面)的数据。多面聚合提供多个过滤器和分类来指导数据浏览和分析。分面的一个常见实现是有多少在线零售商提供了通过对产品价格、制造商、尺寸和其他因素应用过滤器来缩小搜索结果的范围。 您可以FacetOperation使用类的facet()方法定义一个Aggregation。您可以使用and()方法使用多个聚合管道对其进行自定义。每个子管道在输出文档中都有自己的字段,其结果存储为文档数组。 子管道可以在分组之前投影和过滤输入文档。常见用例包括在分类之前提取日期部分或计算。以下清单显示了构面操作示例: 示例 103. 分面操作示例 // 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")) 请注意,有关方面操作的更多详细信息可以在 MongoDB 聚合框架参考文档的$facet部分中找到。 按计数排序按计数排序操作根据指定表达式的值对传入文档进行分组,计算每个不同组中的文档计数,并按计数对结果进行排序。它提供了在使用分面分类时应用排序的便捷快捷方式。按计数排序操作需要分组字段或分组表达式。以下清单显示了按计数排序的示例: 示例 104. 按计数排序示例 // generates { $sortByCount: "$country" } sortByCount("country"); 按计数排序操作等效于以下 BSON(二进制 JSON): { $group: { _id: <表达式>, 计数: { $sum: 1 } } }, { $sort: { 计数: -1 } } 投影表达式中的 Spring 表达式支持我们通过和类的andExpression方法支持在投影表达式中使用 SpEL 表达式。此功能可让您将所需的表达式定义为 SpEL 表达式。在运行查询时,SpEL 表达式被转换为相应的 MongoDB 投影表达式部分。这种安排使得表达复杂计算变得更加容易。 使用 SpEL 表达式进行复杂计算考虑以下 SpEL 表达式: 1 + (q + 1) / (q - 1) 前面的表达式被翻译成下面的投影表达式部分: { "$add" : [ 1, { "$divide" : [ { "$add":["$q", 1]}, { "$subtract":[ "$q", 1]} ] }]} 您可以在聚合框架示例 5和聚合框架示例 6 中查看更多上下文中的示例。您可以在 中找到有关受支持的 SpEL 表达式构造的更多使用示例 除了上表中显示的转换之外,您还可以使用标准 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(); 前面的清单使用以下算法:
请注意,输入集合被明确指定为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); 请注意,ZipInfo该类映射给定输入集合的结构。在ZipInfoStats类定义了在所需的输出格式的结构。 前面的清单使用以下算法:
请注意,我们从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(); 前面的清单使用以下算法:
请注意,我们从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 表达式派生的复杂算术运算。 注意:传递给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。我们discount通过对所有qty大于或等于 的库存项目使用条件运算来投影该字段250。对该description字段执行第二个条件投影。我们将Unspecified描述应用于所有没有description字段或有null描述的项目。 从 MongoDB 3.6 开始,可以使用条件表达式从投影中排除字段。 示例 105. 条件聚合投影 TypedAggregation<Book> agg = Aggregation.newAggregation(Book.class, project("title") .and(ConditionalOperators.when(ComparisonOperators.valueOf("author.middle") .equalToValue("")) .then("$$REMOVE") .otherwiseValueOf("author.middle") ) .as("author.middle")); 如果字段的值 author.middle 不包含值, 然后用于$$REMOVE排除该字段。 否则,添加 的字段值author.middle。 |
|
来自: 王先生的内容 > 《Spring国际认证》