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

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->

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

Example

The 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官网的例子。

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值