本例使用条件投影,类似我们熟悉的 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"]}} // }] } }