如何在Spring Data中从MongoDB文档数组返回指定ID的自定义Travel对象?
Spring Data MongoDB聚合查询返回自定义对象问题
集合结构
[ { "_id":"1", "travels":[ {"_id":"1"}, {"_id":"2"}, {"_id":"3"} ] }, { "_id":"2", "travels":[ {"_id":"4"}, {"_id":"5"}, {"_id":"6"} ] } ]
Java模型
public class TravelsDocument<T> { @Id public String id; public List<Travel> travels; }
public class Travel { public String id; }
需求:查询匹配指定id的单个Travel元素(已知travels数组内的id全局唯一)。
用户尝试的MongoDB原生查询逻辑:
- 过滤条件:
{travels: {$elemMatch: { _id: 'someId'}}}
- 投影操作:
{ "_id":0, "travel":{ "$arrayElemAt":[ "$travels", { "$indexOfArray":[ "$travels._id", "someId" ] } ] } }
用户在Spring中使用@Aggregation的实现:
@Aggregation(pipeline = { "{'$match' :{'travels': {$elemMatch: { _id: :#{#id}}}}}", "{'$project' :{_id:0, 'travel':{$arrayElemAt: [ '$travels', {$indexOfArray :['$travels._id', :#{#id}]} ] }}}" }) Mono<Travel> findSingleTravelByTenantAndId(@Param("id") String id);
问题:方法返回org.bson.Document而非自定义的Travel对象,询问是否可通过@Aggregation返回自定义对象,以及该实现方式是否可行。
解决方案
1. 调整聚合管道,让结果直接匹配Travel结构
当前投影会把结果包裹在travel字段中,Spring Data无法直接映射到Travel对象。可以添加$replaceRoot阶段将匹配元素提升为根文档:
@Aggregation(pipeline = { "{'$match' :{'travels._id': :#{#id}}}", "{'$project' :{_id:0, item: {$arrayElemAt: ['$travels', {$indexOfArray: ['$travels._id', :#{#id}]}]}}}", "{'$replaceRoot': {newRoot: '$item'}}" }) Mono<Travel> findSingleTravelByTenantAndId(@Param("id") String id);
说明:
$match阶段简化为直接匹配travels._id,单条件匹配数组元素无需$elemMatch$project将匹配元素存入item字段$replaceRoot把item内容设为根文档,返回结构与Travel完全一致,Spring Data可自动映射
2. 使用DTO中转(可选)
如果不想修改聚合管道,可定义一个包含Travel字段的DTO:
public class TravelResult { private Travel travel; // 生成getter、setter }
修改方法返回类型为Mono<TravelResult>,后续从中提取Travel对象:
@Aggregation(pipeline = { "{'$match' :{'travels': {$elemMatch: { _id: :#{#id}}}}}", "{'$project' :{_id:0, 'travel':{$arrayElemAt: [ '$travels', {$indexOfArray :['$travels._id', :#{#id}]} ] }}}" }) Mono<TravelResult> findSingleTravelByTenantAndId(@Param("id") String id);
调用示例:
travelRepository.findSingleTravelByTenantAndId("someId") .map(TravelResult::getTravel) .subscribe(travel -> { /* 业务处理 */ });
3. 实现可行性验证
你的核心逻辑是可行的,问题仅在于投影结果结构与目标对象不匹配。通过上述两种方式,均可让Spring Data正确映射到自定义Travel对象。
另外,因travels数组id唯一,也可使用$unwind+$match的方式实现,不过$indexOfArray+$arrayElemAt的组合性能更优:
@Aggregation(pipeline = { "{'$match' :{'travels._id': :#{#id}}}", "{'$unwind': '$travels'}", "{'$match' :{'travels._id': :#{#id}}}", "{'$replaceRoot': {newRoot: '$travels'}}", "{'$limit': 1}" }) Mono<Travel> findSingleTravelByTenantAndId(@Param("id") String id);
内容的提问来源于stack exchange,提问作者nanotasza
相关产品推荐
相关产品推荐

