You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Mongoose中为文档插入触发函数?解决upsert失效问题

问题:MongoDB upsert操作无法触发save钩子中的isNew判断,如何仅在创建元素时触发函数并获取ID?

需求与尝试

需求:每次在集合中插入/创建元素时触发函数(更新时不触发),且需获取新创建元素的ID。

我尝试通过Mongoose的save钩子实现:

schema.pre("save", function() {
    this.$locals.wasNew = this.isNew;
});

schema.post("save", function() {
    if (this.$locals.wasNew) {
        console.log(`User ${this.id} created!`);
    }
});

这段代码在调用UserModel.create()时完全正常:

await UserModel.create({
    mail
});

但当我调用带upsert: true的findOneAndUpdate()时,钩子完全没触发预期的逻辑:

const user = await UserModel.findOneAndUpdate(
    { mail },
    { $set: { mail } },
    { new: true, upsert: true }
);

疑问:upsert操作似乎没触发isNew为true的save钩子,是我操作错了吗?有没有其他靠谱的实现方式?


原因与解决方案

为什么save钩子不生效?

findOneAndUpdate(包括带upsert的场景)是MongoDB的原子操作,默认不会走Mongoose的文档实例化流程——也就是说它直接在数据库层面执行更新/插入,不会创建Mongoose文档对象,自然也就不会触发save钩子,更不会设置isNew属性。

替代方案

方案1:监听findOneAndUpdate的post钩子(推荐)

直接监听findOneAndUpdate的后置钩子,通过MongoDB返回的原始结果判断是否是新创建的文档:

schema.post('findOneAndUpdate', function(doc, res) {
  // rawResult里的upserted字段存在,说明是upsert创建的新文档
  if (res.rawResult?.upserted) {
    const newUserId = res.rawResult.upserted[0]._id;
    console.log(`User ${newUserId} created!`);
  }
});

注意:调用findOneAndUpdate时必须加上rawResult: true参数,才能拿到包含upsert信息的原始结果:

const user = await UserModel.findOneAndUpdate(
    { mail },
    { $set: { mail } },
    { new: true, upsert: true, rawResult: true }
);

方案2:强制触发save钩子(不推荐)

如果非要复用save钩子的逻辑,可以在调用findOneAndUpdate时添加runValidators: true和context: 'query'参数,强制让Mongoose实例化文档并触发save钩子。但这种方式会破坏原子性,性能不如原生钩子,只适合特殊场景:

const user = await UserModel.findOneAndUpdate(
    { mail },
    { $set: { mail } },
    { new: true, upsert: true, runValidators: true, context: 'query' }
);

方案3:统一使用create/save方法

如果业务逻辑允许,尽量统一用Model.create()或者先查询文档,不存在时再创建并调用doc.save(),这样就能正常触发save钩子中的isNew判断,逻辑更统一。


内容的提问来源于stack exchange,提问作者Herobrine

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.24 03:40:06