如何将Mongoose字段默认值设为数据库现有条目数量?
解决Mongoose Schema中动态生成连续唯一排序索引的问题
我之前正好处理过类似的需求——要给新创建的文档生成一个默认的连续唯一排序值,同时允许用户手动修改。直接在Schema的default选项里写是行不通的,因为default只能是静态值或者同步函数,没法动态查询数据库里的已有条目数量。下面给你两种可行的实现方案,兼顾基本场景和并发安全:
方案一:基础预保存中间件(适合低并发场景)
利用Mongoose的pre('save')预保存钩子,在文档保存前动态查询当前集合的文档数量,以此作为sorting的默认值:
const CategorySchema = Schema({ title: String, sorting: { type: Number, index: { unique: true }, // 这里不设置默认值,交给中间件处理 }, creationDate: { type: Date, default: Date.now }, }); // 预保存中间件:仅对新创建且未手动设置sorting的文档生效 CategorySchema.pre('save', async function(next) { if (this.isNew && typeof this.sorting === 'undefined') { try { // 获取当前Category集合的文档总数 const docCount = await this.constructor.countDocuments(); // 第一个文档count为0,正好对应sorting=0,后续依次递增 this.sorting = docCount; next(); } catch (error) { next(error); } } else { // 若已手动设置sorting或不是新文档,直接进入下一步 next(); } }); const Category = mongoose.model('Category', CategorySchema);
注意事项
这个方案在低并发场景下完全够用,但如果有多个请求同时创建文档,可能会出现多个请求拿到相同的docCount,导致sorting重复触发唯一索引冲突。如果你的业务有高并发创建的需求,建议用下面的方案。
方案二:原子计数器(高并发安全)
通过创建一个独立的计数器集合,利用MongoDB的findOneAndUpdate原子操作来递增计数,彻底避免并发冲突:
步骤1:定义计数器Schema
// 创建专门的计数器模型,记录每个集合的当前最大排序值 const CounterSchema = new Schema({ modelName: { type: String, required: true, unique: true }, currentCount: { type: Number, default: 0 } }); const Counter = mongoose.model('Counter', CounterSchema);
步骤2:修改Category的预保存中间件
const CategorySchema = Schema({ title: String, sorting: { type: Number, index: { unique: true }, }, creationDate: { type: Date, default: Date.now }, }); CategorySchema.pre('save', async function(next) { if (this.isNew && typeof this.sorting === 'undefined') { try { // 原子操作:找到对应模型的计数器,不存在则创建,同时递增计数 const updatedCounter = await Counter.findOneAndUpdate( { modelName: 'Category' }, { $inc: { currentCount: 1 } }, { upsert: true, new: true } // upsert表示不存在则插入,new返回更新后的文档 ); // 因为第一次递增后currentCount是1,所以sorting要减1,保证第一个文档是0 this.sorting = updatedCounter.currentCount - 1; next(); } catch (error) { next(error); } } else { next(); } }); const Category = mongoose.model('Category', CategorySchema);
优势
findOneAndUpdate是MongoDB的原子操作,多个请求同时触发时会排队执行,不会出现计数重复的问题- 计数器独立存储,后续如果需要重置排序值或者批量调整,操作更灵活
额外提示
- 如果用户手动设置了
sorting值,中间件会自动跳过默认值生成逻辑,满足自定义排序的需求 - 要记得在业务代码中捕获
DuplicateKeyError(当手动设置的sorting值已存在时抛出),给用户友好的错误提示
内容的提问来源于stack exchange,提问作者StegSchreck
相关产品推荐
相关产品推荐

