本例使用条件投影,类似我们熟悉的 if-else 语法,或者是三目运算符。条件投影对应 MongoDB 的 $cond 命令。$cond 含义如下:
$cond 命令用来根据指定的布尔表达式结果,返回 $cond 命令指定的两个返回表达式之一。$cond 命令有两种语法:
{ $cond: { if: <boolean-expression>, then: <true-case>, else: <false-case> } }或者
{ $cond: [ <boolean-expression>, <true-case>, <false-case> ] }注意:$cond 的任意一个语法都需要所有三个参数,即 <boolean-expression>、<true-case>、<false-case>。如果 <boolean-expression> 表达式计算结果为 true,则 $cond 计算并返回 <true-case> 表达式的值。否则 $cond 计算并返回 <false-case> 表达式的值。并且,$cond 的参数可以是任何有效的表达式。
该示例关键代码:
(1)输入文档实体
public class InventoryItem {
   @Id
   private int id;
   private String item;
   private String description;
   private int qty;
}(2)输出文档实体
public class InventoryItemProjection {
   @Id
   private int id;
   private String item;
   private String description;
   private int qty;
   private int discount;
}(3)投影关键代码
TypedAggregation<InventoryItem> agg = newAggregation(InventoryItem.class,
   project("item")
       // 如果 qty 大于等于 250,则为 30,否则为 20
       .and("discount").applyCondition(
           ConditionalOperators.Cond.newBuilder().when(Criteria.where("qty").gte(250)
       ).then(30).otherwise(20))
       // 如果 description 为 null,则为 Unspecified,否则返回原值
       .and(ConditionalOperators.ifNull("description")
               .then("Unspecified")).as("description")
);
AggregationResults<InventoryItemProjection> result = mongoTemplate.aggregate(agg, InventoryItemProjection.class);
List<InventoryItemProjection> stateStatsList = result.getMappedResults();
for(InventoryItemProjection doc : stateStatsList) {
   System.out.println(JSONObject.toJSONString(doc));
}上面代码对 inventoryItem 集合使用投影操作。通过对 qty 字段大于或等于 250 的所有文档使用条件操作来计算 discount 新字段,并对 description 字段执行第二个条件投影。如果我们未指定 description 字段,则使用 “Unspecified” 作为默认字段。否则,直接使用 description 字段的值。
注意,从 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 的字段值。
(1)application.properties 配置
# Log logging.level.root=debug # MongoDB spring.data.mongodb.uri=mongodb://localhost:27017/test
(2)AppConfig.java 配置类
package com.hxstrive.springdata.mongodb.config;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.mongodb.MongoDatabaseFactory;
import org.springframework.data.mongodb.core.MongoTemplate;
/**
* 配置 MongoTemplate
* @author hxstrive.com 2022/12/23
*/
@Slf4j
@Configuration
public class AppConfig {
   @Bean
   public MongoTemplate mongoTemplate(MongoDatabaseFactory mongoDatabaseFactory) {
       log.info("mongoTemplate({}, {})", mongoDatabaseFactory);
       return new MongoTemplate(mongoDatabaseFactory);
   }
}(3)输入文档实体
import lombok.Builder;
import lombok.Data;
import org.springframework.data.annotation.Id;
/**
* 输入文档类型实体
* @author hxstrive.com
*/
@Data
@Builder
public class InventoryItem {
   @Id
   private int id;
   private String item;
   private String description;
   private int qty;
}(4)投影输出结果实体
import lombok.Data;
import org.springframework.data.annotation.Id;
/**
* 投影结果
* @author hxstrive.com
*/
@Data
public class InventoryItemProjection {
   @Id
   private int id;
   private String item;
   private String description;
   private int qty;
   private int discount;
}(5)客户端代码
import com.alibaba.fastjson.JSONObject;
import com.hxstrive.springdata.mongodb.entity.demo7.InventoryItem;
import com.hxstrive.springdata.mongodb.entity.demo7.InventoryItemProjection;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.aggregation.AggregationResults;
import org.springframework.data.mongodb.core.aggregation.ConditionalOperators;
import org.springframework.data.mongodb.core.aggregation.TypedAggregation;
import org.springframework.data.mongodb.core.query.Criteria;
import java.util.List;
import static org.springframework.data.mongodb.core.aggregation.Aggregation.newAggregation;
import static org.springframework.data.mongodb.core.aggregation.Aggregation.project;
/**
* 聚合框架示例7
* @author hxstrive.com
*/
@SpringBootTest
public class AggregationFrameworkDemo7 {
   @Autowired
   private MongoTemplate mongoTemplate;
   @BeforeEach
   public void init() {
       mongoTemplate.dropCollection(InventoryItem.class);
       // 准备数据
       mongoTemplate.insert(InventoryItem.builder().id(1).item("A").description("description1").qty(120).build());
       mongoTemplate.insert(InventoryItem.builder().id(2).item("B").description("").qty(300).build());
       mongoTemplate.insert(InventoryItem.builder().id(3).item("C").description(null).qty(400).build());
   }
   @Test
   public void contextLoads() {
       TypedAggregation<InventoryItem> agg = newAggregation(InventoryItem.class,
           project("item")
               // 如果 qty 大于等于 250,则为 30,否则为 20
               .and("discount").applyCondition(
                   ConditionalOperators.Cond.newBuilder().when(Criteria.where("qty").gte(250)
               ).then(30).otherwise(20))
               // 如果 description 为 null,则为 Unspecified,否则返回原值
               .and(ConditionalOperators.ifNull("description")
                       .then("Unspecified")).as("description")
       );
       AggregationResults<InventoryItemProjection> result = mongoTemplate.aggregate(agg, InventoryItemProjection.class);
       List<InventoryItemProjection> stateStatsList = result.getMappedResults();
       for(InventoryItemProjection doc : stateStatsList) {
           System.out.println(JSONObject.toJSONString(doc));
       }
       // 结果:
       // {"description":"description1","discount":20,"id":1,"item":"A","qty":0}
       // {"description":"","discount":30,"id":2,"item":"B","qty":0}
       // {"description":"Unspecified","discount":30,"id":3,"item":"C","qty":0}
       // 执行的语句如下:
       // [{ "$project" : { "item" : 1, "discount" : {
       //    "$cond" : { "if" : { "$gte" : ["$qty", 250]}, "then" : 30, "else" : 20}},
       //    "description" : { "$ifNull" : ["$description", "Unspecified"]}}
       // }]
   }
}
            