Mongoose设置min校验规则后更新文档未触发校验问题
问题原因
- 默认更新操作不触发校验:Mongoose 的 Schema 校验规则默认仅对
save()、create()等文档实例操作生效,updateOne()、findByIdAndUpdate()等更新操作默认关闭校验,需要手动开启。 - 原子操作校验逻辑不匹配:就算开启了更新校验,你配置的
min: 1只会校验你直接设置给amount的字段值,而你用的是$inc原子增量操作,传入的增量值-1本身是合法数字,Mongoose 默认不会主动计算增量后的最终值是否符合min约束,因此校验不会触发。
解决方法
方案1:先查后改+save()触发全量校验
先查询到对应文档实例,修改数值后调用save()方法,会自动触发Schema的全量校验,小于1会抛出校验错误。
const cart = await CartModel.findById(cartId); if (!cart) throw new Error('购物车记录不存在'); switch (type) { case "inc": cart.amount += 1; break; case "dec": cart.amount -= 1; break; default: throw Error("Bad request"); } await cart.save(); // 这里会触发min校验,amount<1会直接抛错
缺点是需要两次数据库请求,高并发场景下可能存在竞态问题,需要配合事务或者乐观锁处理。
方案2:更新时增加过滤条件(推荐,原子操作无并发问题)
在执行dec减量操作时,给查询过滤条件加上amount: { $gt: 1 },只有当前amount大于1的时候才会执行更新,从根源上避免减到小于1的情况,性能最优且是原子操作,不会有并发问题:
const filter = { _id: cartId }; if (type === 'dec') { filter.amount = { $gt: 1 }; // 只有当前amount>1才允许减量 } const res = await CartModel.updateOne(filter, update); if (res.matchedCount === 0 && type === 'dec') { throw new Error('商品数量不能小于1'); }
方案3:开启更新校验+自定义中间件校验
首先在所有更新操作的配置中开启runValidators: true,再给Schema增加更新前置中间件,自行判断增量后的结果是否符合要求:
// 开启更新校验的调用示例 await CartModel.updateOne({ _id: cartId }, update, { runValidators: true }); // Schema增加前置更新中间件 CartSchema.pre('updateOne', function(next) { const update = this.getUpdate(); // 检查是否是$inc操作修改amount if (update?.$inc?.amount && update.$inc.amount < 0) { const decVal = Math.abs(update.$inc.amount); // 给查询条件加校验,避免更新后小于1 this.setQuery({ ...this.getQuery(), amount: { $gte: decVal + 1 } }); } next(); })
内容的提问来源于stack exchange,提问作者mxxnseat
相关产品推荐
相关产品推荐

