基于Spring Data MongoDB 2.7.3实现指定城市匹配的聚合查询
问题描述
现有MongoDB文档结构
country{id="1", name="France", cities=[{detail="new_city"}, {from="Paris"}, {from="Lyon"} ]} country{id="2", name="UK", cities=[{detail="new_city"}, {from="Paris"}, {from="London"}, {from="Liverpool"} ]}
需求场景
- 当搜索
from="Paris"时,若cities数组中存在该元素,返回对应文档并将cities字段过滤为该元素,输出如下:
country{id="1", name="France", cities={from="Paris"}} country{id="2", name="UK", cities={from="Paris"}}
- 当搜索
from="Los Angeles"时,若cities数组中不存在该元素,返回对应文档并将cities字段过滤为含detail="new_city"的元素,输出如下:
country{id="1", name="France", cities={detail="new_city"}} country{id="2", name="UK", cities={detail="new_city"}}
使用Spring Boot版本为spring-boot-starter-data-mongodb 2.7.3,如何通过聚合(aggregation)实现上述需求?
实现方案
核心逻辑
先通过数组筛选分别提取目标from元素和new_city元素,再根据目标元素是否存在做条件判断,最终替换原cities字段为符合要求的单个元素。
原生MongoDB聚合管道示例
先看原生聚合语句,便于理解逻辑:
db.country.aggregate([ // 筛选出匹配目标from的元素、以及new_city元素 { $addFields: { matchedCities: { $filter: { input: "$cities", cond: { $eq: ["$$this.from", "Paris"] } // 替换为用户搜索参数 } }, newCity: { $filter: { input: "$cities", cond: { $eq: ["$$this.detail", "new_city"] } } } } }, // 根据matchedCities是否为空,选择最终的cities值 { $project: { id: 1, name: 1, cities: { $cond: { if: { $gt: [{ $size: "$matchedCities" }, 0] }, then: { $arrayElemAt: ["$matchedCities", 0] }, else: { $arrayElemAt: ["$newCity", 0] } } } } } ])
Spring Boot Data MongoDB 代码实现
结合spring-boot-starter-data-mongodb 2.7.3的API,用Java代码构建聚合管道:
import org.springframework.data.mongodb.core.MongoTemplate; import org.springframework.data.mongodb.core.aggregation.Aggregation; import org.springframework.data.mongodb.core.aggregation.AggregationOperation; import org.springframework.data.mongodb.core.aggregation.ConditionalOperators; import org.springframework.data.mongodb.core.aggregation.ProjectionOperation; import org.springframework.data.mongodb.core.aggregation.ArrayOperators; import org.springframework.data.mongodb.core.query.Criteria; import java.util.List; // 注入MongoTemplate private final MongoTemplate mongoTemplate; public List<Country> getFilteredCountries(String targetFrom) { // 1. 筛选出匹配目标from的cities元素 AggregationOperation addMatchedCities = Aggregation.addFields() .field("matchedCities") .withValue(ArrayOperators.Filter.filter("cities") .as("city") .by(Criteria.where("city.from").is(targetFrom))) .build(); // 2. 筛选出detail为new_city的cities元素 AggregationOperation addNewCity = Aggregation.addFields() .field("newCity") .withValue(ArrayOperators.Filter.filter("cities") .as("city") .by(Criteria.where("city.detail").is("new_city"))) .build(); // 3. 投影操作:根据matchedCities是否为空,选择最终的cities值 ProjectionOperation projection = Aggregation.project("id", "name") .and(ConditionalOperators.Cond.when(ArrayOperators.Size.lengthOfArray("matchedCities").greaterThanValue(0)) .then(ArrayOperators.ArrayElemAt.arrayElementAt("matchedCities", 0)) .otherwise(ArrayOperators.ArrayElemAt.arrayElementAt("newCity", 0))) .as("cities"); // 组装并执行聚合 Aggregation aggregation = Aggregation.newAggregation(addMatchedCities, addNewCity, projection); return mongoTemplate.aggregate(aggregation, "country", Country.class).getMappedResults(); } // 对应的实体类示例 class Country { private String id; private String name; private City cities; // 对应需求的单个对象输出,若需数组可改为List<City> // getter、setter、构造方法省略 } class City { private String detail; private String from; // getter、setter、构造方法省略 }
代码说明
- 用
ArrayOperators.Filter实现数组元素的精准筛选,分别得到匹配目标from的元素列表和new_city元素列表 - 通过
ConditionalOperators.Cond做分支判断:如果匹配到目标from元素,就取第一个匹配项;否则取new_city元素 - 实体类
Country的cities字段定义为单个City对象,与需求输出格式一致,若需要保留数组格式可修改为List<City>
内容的提问来源于stack exchange,提问作者Adam D
相关产品推荐
相关产品推荐

