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