spring-data-mongodb-1.9.x中Aggregation关于Conditional Aggregation Operators的坑

来源:互联网 发布:淘宝账户名怎么修改 编辑:程序博客网 时间:2024/06/05 17:34

spring-data-mongodb-1.9.x中Aggregation关于Conditional Aggregation Operators的坑

截止至发布该文章前,spring-data-mongodb稳定版本还是1.9.x,本文中所提到的坑主要针对1.9.x或之前的版本,2.0.x版本的可以忽略本篇文章或直接浏览本文最后说明内容。

MongoDB中聚合(aggregate)主要用于处理数据(诸如统计平均值,求和,分组等),并返回计算后的数据结果。有点类似sql语句中的 count(*),group by。MongoDB中通过在aggregate方法中添加一系列管道(pipeline)来达到数据处理的目的。(关于MongoDB aggregate的详细介绍、语法、使用说明等这里不做具体说明,大家自己查阅其他资料进行了解。)
Aggregate 中有一种操作方式是Conditional Aggregation Operators,先看看官方文档中的说明$cond (aggregation):

$cond (aggregation)Evaluates a boolean expression to return one of the two specified return expressions.The $cond expression has one of two syntaxes:New in version 2.6.{ $cond: { if: <boolean-expression>, then: <true-case>, else: <false-case-> } }Or:{ $cond: [ <boolean-expression>, <true-case>, <false-case> ] }If the <boolean-expression> evaluates to true, then $cond evaluates and returns the value of the <true-case> expression. Otherwise, $cond evaluates and returns the value of the <false-case> expression.The arguments can be any valid expression. For more information on expressions, see Expressions.

翻译成java语法就是一个简单的三目运算符:

<boolean-expression>?<true-case>:<false-case->

再来看看官网上的例子还是上面那地址:

ExampleThe following example use a inventory collection with the following documents:{ "_id" : 1, "item" : "abc1", qty: 300 }{ "_id" : 2, "item" : "abc2", qty: 200 }{ "_id" : 3, "item" : "xyz1", qty: 250 }The following aggregation operation uses the $cond expression to set the discount value to 30 if qty value is greater than or equal to 250 and to 20 if qty value is less than 250:db.inventory.aggregate(   [      {         $project:           {             item: 1,             discount:               {                 $cond: { if: { $gte: [ "$qty", 250 ] }, then: 30, else: 20 }               }           }      }   ])The operation returns the following results:{ "_id" : 1, "item" : "abc1", "discount" : 30 }{ "_id" : 2, "item" : "abc2", "discount" : 20 }{ "_id" : 3, "item" : "xyz1", "discount" : 30 }The following operation uses the array syntax of the $cond expression and returns the same results:db.inventory.aggregate(   [      {         $project:           {             item: 1,             discount:               {                 $cond: [ { $gte: [ "$qty", 250 ] }, 30, 20 ]               }           }      }   ])

例子很简单,通过$cond表达式,将qty值大于等于250的记录的discount值设为30,qty值小于250的记录的discount值设为20。

要实现上面的效果,在spring-data-mongodb中该怎么操作呢?

很遗憾,在spring-data-mongodb 1.9.x 的Reference 文档中并没有找到Conditional Aggregation Operators相关的例子,那就只能通过API中内容来找到想要的内容了。
org.springframework.data.mongodb.core.aggregation.ProjectionOperation.ProjectionOperationBuilder类中发现了这个一个方法:

public ProjectionOperation.ProjectionOperationBuilder project(String operation, Object... values)Adds a generic projection for the current field.Parameters:operation - the operation key, e.g. $add.values - the values to be set for the projection operation.Returns:

那就试试这个方法吧,还是采用上面这个例子(代码片段,无法执行):

import static org.springframework.data.mongodb.core.aggregation.Aggregation.*;Aggregation agg = newAggregation(    project("item").and("qty").project("cond", 30, 20).as("discount")    //project("item").and("qty").project("cond", "qty >= 250", 30, 20).as("discount")    //project("item").and("qty >= 250").project("cond", 30, 20).as("discount")    //project("item").andExpression("qty >= 250").project("cond", 30, 20).as("discount"));mongoTemplate.aggregate(agg, "collectionName", OutputType.class);

经过尝试,发现上诉四种方式都无法达到我们想要的目的(结果不对,语法错误等)。

难道就spring-data-mongodb就无法使用$cond了吗?上个大招吧!!!
首先,自己定义一个新的AggregationOperation实现类来实现spring-data-mongodb的AggregationOperation:

import org.springframework.data.mongodb.core.aggregation.AggregationOperation;import org.springframework.data.mongodb.core.aggregation.AggregationOperationContext;import com.mongodb.DBObject;public class DBObjectAggregationOperation implements AggregationOperation {    private DBObject operation;    public DBObjectAggregationOperation (DBObject operation) {        this.operation = operation;    }    @Override    public DBObject toDBObject(AggregationOperationContext context) {        return context.getMappedObject(operation);    }}

然后,我们就可以在代码中这样实现我们的功能:

import static org.springframework.data.mongodb.core.aggregation.Aggregation.*;/*** 通过DBObject 来创建mongodb相同的project语句* $project:*   {*       item: 1,*       iscount:*           {*               $cond: [ { $gte: [ "$qty", 250 ] }, 30, *20 ]*           }*    }*/DBObject operation = (DBObject) new BasicDBObject("$project", new BasicDBObject("discount",     new BasicDBObject("$cond", new Object[] {        new BasicDBObject("$gte", new Object[] {            "$qty",250         }), 30, 20 }).append("item", 1)));Aggregation agg = newAggregation(    new DBObjectAggregationOperation(operation));mongoTemplate.aggregate(agg, "collectionName", OutputType.class);

经过测试,上诉代码能得到我们想要的结果。

mongodb的操作命令语法都是采用json(bson)格式,在处理复杂的命令时都可以通过类似上诉方法通过DBObject组装命令,然后通过spring-data-mongodb来执行得到相应的结果。

说明1:
在spring-data-mongodb 2.0.x 的Reference的文档中已经可以找到ConditionalOperator相关例子了Aggregation Framework Example 7:

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

github的代码中也找了相关实现类ConditionalOperator,

    org.springframework.data.mongodb.core.aggregation.ConditionalOperator 

后续可以通过上述例子中的代码来实现MongoDB官网的例子。

1 0
原创粉丝点击