如何创建便于类型化反序列化的MongoDB聚合Lookup查询
MongoDB聚合Lookup结果结构调整方案
集合结构
PERSON 集合
{ "_id":{"$oid":"64a20e0c65e828693428a8c1"}, "username":"username_0" }
CONTACT 集合
{ "_id":{"$oid":"64a20e0c65e828693428a8c5"}, "person_id":{"$oid":"64a20e0c65e828693428a8c1"}, // 关联Person的ID "data":"data_0" }
MESSAGE 集合
{ "_id":{"$oid":"64a278f583364c2f4f3c81c8"}, "contact_receiver_id":{"$oid":"64a20e0c65e828693428a8c5"}, // 关联Contact的ID "contact_sender_id":{"$oid":"64a20e0c65e828693428a8d3"} // 关联Contact的ID }
期望返回结果结构
{ "person": { // 所有用户信息包裹在此字段中 "_id": { "$oid": "64a20e0c65e828693428a8c1" }, "username": "username_0" }, "contacts": [ { "contact": { // 所有联系人信息包裹在此字段中 "_id": { "$oid": "64a20e0c65e828693428a8c5" }, "person_id": { "$oid": "64a20e0c65e828693428a8c1" }, "data": "data_0" }, "messages": [ { "_id": { "$oid": "64a20e0c65e828693428a8c5" }, "contact_receiver_id":{"$oid":"64a20e0c65e828693428a8c1"}, "contact_sender_id":{"$oid":"64a20e0c65e828693428a8d3"} } // 更多消息 ] } // 更多联系人 ] }
当前问题
已实现嵌套Lookup,但返回结果未按要求包裹字段,当前返回结构如下:
{ "_id": { "$oid": "64a20e0c65e828693428a8c1" }, "username": "username_0", "contacts": [ { "_id": { "$oid": "64a20e0c65e828693428a8c5" }, "person_id": { "$oid": "64a20e0c65e828693428a8c1" }, "data": "data_0", "messages": [ { "_id": { "$oid": "64a20e0c65e828693428a8c5" }, "contact_receiver_id":{"$oid":"64a20e0c65e828693428a8c1"}, "contact_sender_id":{"$oid":"64a20e0c65e828693428a8d3"} } // 更多消息 ] } // 更多联系人 ] }
动机
使用Kotlin MongoDB驱动,希望直接用以下数据类序列化聚合结果,避免重复定义包含额外字段的领域对象:
@Serializable data class DbContactsMessages( val contact: Contact, // 领域类 val messages: List<Message> ) @Serializable data class UserConctactsMessages( val person: Person, // 领域类 val contacts: List<DbContactsMessages> )
解决方案
通过聚合管道中的$lookup嵌套管道、$project和$addFields来调整结果结构,具体聚合查询如下:
db.person.aggregate([ // 匹配目标用户(可根据业务调整条件) { $match: { _id: ObjectId("64a20e0c65e828693428a8c1") } }, // 关联Contact集合并处理嵌套结构 { $lookup: { from: "contact", localField: "_id", foreignField: "person_id", as: "contacts", pipeline: [ // 关联当前联系人的所有消息 { $lookup: { from: "message", let: { contactId: "$_id" }, pipeline: [ { $match: { $expr: { $or: [ { $eq: ["$contact_receiver_id", "$$contactId"] }, { $eq: ["$contact_sender_id", "$$contactId"] } ] } } } ], as: "messages" } }, // 将联系人信息包裹到contact字段 { $project: { contact: "$$ROOT", messages: 1 } }, // 移除contact字段中冗余的messages属性 { $addFields: { "contact.messages": "$$REMOVE" } } ] } }, // 将用户信息包裹到person字段 { $project: { person: "$$ROOT", contacts: 1 } }, // 移除person字段中冗余的contacts属性 { $addFields: { "person.contacts": "$$REMOVE" } } ])
关键步骤说明
- 用户匹配:
$match阶段定位目标用户,可根据业务需求修改匹配条件。 - 联系人与消息关联:
- 在关联Contact的
$lookup中嵌套管道,先通过$lookup获取该联系人的所有消息。 - 用
$project将当前联系人的完整数据($$ROOT)放入contact字段,同时保留messages列表。 - 通过
$addFields移除contact字段中自动带出的冗余messages属性。
- 在关联Contact的
- 用户结构调整:
- 用
$project将用户的完整数据放入person字段,保留contacts列表。 - 再次使用
$addFields移除person字段中冗余的contacts属性。
- 用
执行以上聚合查询后,返回结果将完全匹配你定义的Kotlin数据类结构,无需额外定义重复的领域对象。
内容的提问来源于stack exchange,提问作者user15861750
相关产品推荐
相关产品推荐

