文档中间件、模型中间件、聚合中间件、查询中间件有啥区别?

Posted

技术标签:

【中文标题】文档中间件、模型中间件、聚合中间件、查询中间件有啥区别?【英文标题】:what is the difference between document middleware, model middleware, aggregate middleware, and query middleware?文档中间件、模型中间件、聚合中间件、查询中间件有什么区别? 【发布时间】:2020-11-15 08:37:16 【问题描述】:

我对 MongoDBMongoose 还很陌生,我真的很困惑为什么有些中间件适用于文档而有些适用于查询。我也很困惑为什么一些查询方法返回文档和一些返回查询。如果查询返回文档是可以接受的,但是为什么查询返回查询以及它到底是什么。

向我的问题添加更多内容,什么是 Document 函数和 Model 或 Query 函数,因为它们都有一些常用方法,例如 updateOne

此外,我从猫鼬文档中收集了所有这些疑问。

【问题讨论】:

我也对这些东西感到困惑 您可以查看article 以获得一些可能的答案。 【参考方案1】:

Tl;dr:中间件类型最常定义 pre/post 挂钩中的 this 变量指的是什么:

Middleware Hook 'this' refers to the methods
Document Document validate, save, remove, updateOne, deleteOne, init
Query Query count, countDocuments, deleteMany, deleteOne, estimatedDocumentCount, find, findOne, findOneAndDelete, findOneAndRemove, findOneAndReplace, findOneAndUpdate, remove, replaceOne, update, updateOne, updateMany
Aggregation Aggregation object aggregate
Model Model insertMany

详细解释:

中间件什么都不是,只是以不同方式与数据库交互的内置方法。但是,由于与数据库交互的方式不同,每种方式都有不同的优势或首选用例,它们的行为也各不相同,因此它们的中间件可以有不同的行为,即使它们具有相同的名称。

就其本身而言,中间件只是在 mongoose 引擎下使用的 mongodbs 本机驱动程序的简写/包装器。因此,您通常可以使用所有中间件,就像您使用对象的常规方法一样,而不必关心它是模型、查询、聚合或文档中间件,只要它做您想做的事情。

但是,在一些用例中,区分调用这些方法的上下文很重要。

最突出的用例是钩子。即*.pre()*.post() 钩子。这些钩子是您可以“注入”到您的 mongoose 设置中的方法,以便在特定事件之前或之后执行它们。

例如: 假设我有以下架构:

const productSchema = new Schema(
  name: 'String',
  version: 
    type: 'Number',
    default: 0
  
);

现在,假设您总是想在每次保存时增加版本字段,以便它自动将版本字段增加 1。

最简单的方法是定义一个钩子来为我们处理这个问题,所以我们在保存对象时不必关心这个。例如,如果我们在刚刚创建或从数据库中获取的文档上使用 .save(),我们只需将以下 pre-hook 添加到架构中,如下所示:

 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();
 );

这样做的原因是,对象直接在数据库中更新,而不是首先“下载”到 nodejs,然后再次编辑和“上传”。这意味着,在这个钩子中this 指的是查询而不是文档,这意味着我们不知道version 的当前状态是什么。 如果我们要在这样的查询中增加版本,我们需要按如下方式更新钩子,以便它自动添加 $inc 运算符:

 productSchema.pre('findOneAndUpdate', function(next) 
   this.$inc =   version: 1 ;
   next();
 );

或者,我们可以通过手动获取目标文档并使用异步函数对其进行编辑来模拟之前的逻辑。在这种情况下效率会降低,因为每次更新都会调用 db 两次,但会保持逻辑一致:

productSchema.pre('findOneAndUpdate', async function() 
  const productToUpdate = await this.model.findOne(this.getQuery());
  this.version = productToUpdate.version + 1;
  next();
);

更详细的解释,请检查the official documentation,它也有一个designated paragraph,以解决方法命名冲突的问题(例如remove()既是文档又是查询中间件方法)

【讨论】:

以上是关于文档中间件、模型中间件、聚合中间件、查询中间件有啥区别?的主要内容,如果未能解决你的问题,请参考以下文章

控制器和中间件有啥区别

重放攻击与中间人攻击有啥区别?

Mongoosejs - 我可以在“查找”的后查询中间件中修改文档吗?

App Engine WSGI 中间件分析器

减速器和中间件有啥区别?

Django大纲