如何在MongoDB/Mongoose中优雅实现数组对象的更新或新增?
在MongoDB/Mongoose中实现通知更新逻辑
集合结构
以下是简化后的User集合结构:
[ { _id: "10", groups: ["1"], notifications: [ { notificationType: "message-like", msgId: "3", read: false, likers: ["steve", "joe"] } ] }, { _id: "20", groups: ["1", "2"], notifications: [ { notificationType: "message-like", msgId: "1", read: false, likers: ["frank", "bob"] }, { notificationType: "message-like", msgId: "2", read: false, likers: ["bob"] } ] } ]
需求
- 查找
_id匹配的文档 - 在
notifications数组中查找notificationType和msgId同时匹配的对象 - 若找到匹配对象:
- 向
likers数组添加新元素 - 将该对象移至
notifications数组首位 - 若该对象的
read为true,则将其设为false
- 向
- 若未找到匹配对象:向
notifications数组新增一个通知对象
参考JavaScript逻辑
for (let doc of documents) { if (doc._id === "20") { let notificationFound = false doc.notifications.forEach((notif, i) => { if ( notif.notificationType === "message-like" && notif.msgId === "2" ) { notif.likers.push("joe") if (notif.read === true) notif.read = false doc.notifications.unshift(doc.notifications.splice(i, 1)[0]) // 移至数组首位 notificationFound = true } }) if (!notificationFound) doc.notifications.push(newNotificationObject) } }
MongoDB/Mongoose实现方案
可以通过MongoDB原子更新操作结合聚合表达式实现,避免先查询再修改的非原子操作,保证数据一致性。
1. MongoDB原生语法(findOneAndUpdate)
db.users.findOneAndUpdate( { _id: "20" }, [ { $set: { notifications: { $cond: { // 判断是否存在匹配的通知 if: { $gt: [{ $size: { $filter: { input: "$notifications", cond: { $and: [{ $eq: ["$$this.notificationType", "message-like"] }, { $eq: ["$$this.msgId", "2"] }] } } } }, 0] }, then: { $let: { vars: { // 提取匹配的通知和非匹配的通知 matched: { $first: { $filter: { input: "$notifications", cond: { $and: [{ $eq: ["$$this.notificationType", "message-like"] }, { $eq: ["$$this.msgId", "2"] }] } } }, rest: { $filter: { input: "$notifications", cond: { $not: { $and: [{ $eq: ["$$this.notificationType", "message-like"] }, { $eq: ["$$this.msgId", "2"] }] } } } }, in: [ // 处理匹配通知:添加likers、重置read为false,放在数组首位 { $mergeObjects: [ "$$matched", { likers: { $concatArrays: ["$$matched.likers", ["joe"]] } }, { read: false } ] }, // 拼接剩余通知 "$$rest" ] } }, // 未找到则新增通知 else: { $concatArrays: [ "$notifications", [ { notificationType: "message-like", msgId: "2", read: false, likers: ["joe"] } ] ] } } } } } ], { returnNewDocument: true } // 返回更新后的文档 )
2. Mongoose写法
基于定义好的User模型调用findOneAndUpdate,语法与原生一致:
const updatedUser = await User.findOneAndUpdate( { _id: "20" }, [ { $set: { notifications: { $cond: { if: { $gt: [{ $size: { $filter: { input: "$notifications", cond: { $and: [{ $eq: ["$$this.notificationType", "message-like"] }, { $eq: ["$$this.msgId", "2"] }] } } } }, 0] }, then: { $let: { vars: { matched: { $first: { $filter: { input: "$notifications", cond: { $and: [{ $eq: ["$$this.notificationType", "message-like"] }, { $eq: ["$$this.msgId", "2"] }] } } }, rest: { $filter: { input: "$notifications", cond: { $not: { $and: [{ $eq: ["$$this.notificationType", "message-like"] }, { $eq: ["$$this.msgId", "2"] }] } } } }, in: [ { $mergeObjects: [ "$$matched", { likers: { $concatArrays: ["$$matched.likers", ["joe"]] } }, { read: false } ] }, "$$rest" ] } }, else: { $concatArrays: [ "$notifications", [ { notificationType: "message-like", msgId: "2", read: false, likers: ["joe"] } ] ] } } } } } ], { new: true } // 返回更新后的文档 );
逻辑说明
- 用
$filter筛选出匹配/非匹配的通知项 - 通过
$cond分支处理“存在匹配”和“无匹配”两种场景 - 匹配时:用
$mergeObjects更新通知字段,将处理后的通知放在数组首位再拼接剩余项 - 未匹配时:用
$concatArrays在数组末尾添加新通知 - 整个操作是原子性的,避免并发场景下的数据冲突
内容的提问来源于stack exchange,提问作者Joey Kyber
相关产品推荐
相关产品推荐

