我对MongoDB和Mongoose相对较新,我对为什么有些中间件在文档上运行,有些在查询上运行感到困惑。我也不明白为什么一些查询方法返回文档,而另一些则返回查询。如果查询返回文档是可以接受的,但为什么一个查询会返回查询,那它真正的作用是什么。
进一步地,我的问题是什么是Document函数、Model或Query函数,因为它们都有一些共同的方法,如updateOne
。
此外,我从Mongoose文档中获取了所有这些疑问。
我对MongoDB和Mongoose相对较新,我对为什么有些中间件在文档上运行,有些在查询上运行感到困惑。我也不明白为什么一些查询方法返回文档,而另一些则返回查询。如果查询返回文档是可以接受的,但为什么一个查询会返回查询,那它真正的作用是什么。
进一步地,我的问题是什么是Document函数、Model或Query函数,因为它们都有一些共同的方法,如updateOne
。
此外,我从Mongoose文档中获取了所有这些疑问。
Tl;dr: 中间件类型通常会定义前/后钩子中this
变量的指向:
中间件钩子 | 'this' 指向 | 方法 |
---|---|---|
文档 | 文档 | validate、save、remove、updateOne、deleteOne、init |
查询 | 查询 | count、countDocuments、deleteMany、deleteOne、estimatedDocumentCount、find、findOne、findOneAndDelete、findOneAndRemove、findOneAndReplace、findOneAndUpdate、remove、replaceOne、update、updateOne、updateMany |
聚合 | 聚合对象 | aggregate |
模型 | 模型 | insertMany |
长说明:
中间件是与数据库以不同方式交互的内置方法。然而,由于与数据库交互的方式有不同的优势或首选用例,它们也会彼此不同地表现,因此它们的中间件可能会有不同的行为,即使它们具有相同的名称。
中间件本身只是 mongoose 底层使用的 mongodbs 原生驱动程序的简写/包装。因此,通常可以像使用对象的常规方法一样使用所有中间件,而不必担心它是模型、查询、聚合还是文档中间件,只要它做您想要的。
然而,有几种用例需要区分调用这些方法的上下文。
最突出的用例是钩子。即*.pre()
和*.post()
钩子。这些钩子是您可以“注入”到 mongoose 设置中的方法,以便它们在特定事件之前或之后执行。
例如: 假设我有以下模式:
const productSchema = new Schema({
name: 'String',
version: {
type: 'Number',
default: 0
}
});
.save()
,我们只需要在模式中添加以下前置钩子即可: productSchema.pre('save', function(next) {
this.version = this.version + 1; // or this.version += 1;
next();
});
.save()
多少次,它在实际保存之前总是会递增版本号,即使我们只更改了名称。.save()
或任何其他仅针对文档的中间件,而是使用例如findOneAndUpdate()
等查询中间件来更新对象,该怎么办呢?pre('save')
钩子,因为不会调用.save()
。在这种情况下,我们必须为findOneAndUpdate()
实现类似的钩子。findOneAndUpdate()
钩子将无法允许我们这样做,因为它是查询钩子,意味着它没有访问实际文档,只能访问查询本身。因此,如果我们例如仅更改产品名称,则以下中间件将无法按预期工作: productSchema.pre('findOneAndUpdate', function(next) {
// this.version is undefined in the query and would therefor be NaN
this.version = this.version + 1;
next();
});
this
指的是查询而不是文档,也就是说,我们不知道version
的当前状态。
如果我们要在类似以下的查询中递增版本号,我们需要更新钩子,使其自动添加$inc运算符: productSchema.pre('findOneAndUpdate', function(next) {
this.$inc = { version: 1 };
next();
});
另一种方法是通过手动获取目标文档并使用异步函数进行编辑来模拟先前的逻辑。在这种情况下,效率会降低,因为每次更新都需要两次调用数据库,但可以保持逻辑的一致性:
productSchema.pre('findOneAndUpdate', async function() {
const productToUpdate = await this.model.findOne(this.getQuery());
this.version = productToUpdate.version + 1;
next();
});
如需更详细的解释,请查看官方文档,其中还有一个专门段落讨论方法名称冲突的问题(例如,在Document和Query中都有remove()
作为中间件方法)。