美文网首页
mongoose Api CRUD操作返回结果总结梳理

mongoose Api CRUD操作返回结果总结梳理

作者: AizawaSayo | 来源:发表于2021-03-21 03:18 被阅读0次

    大体概念

    mongoose 中的Model是 由 Schema 编译来的构造函数,类似于JavaScript中的类,负责从底层MongoDB数据库创建和读取文档(CRUD操作);Document就是Model的一个实例,SchemaModel定型,指定Model包含的字段/值的类型/是否必传等等规则;而Query则是定义好的命令,Model的一些辅助函数返回的就是一个Query对象。放在MySQL相当于一条待执行的sql语句,告诉数据库这具体是一个什么操作。

    增加(Create)、检索(Retrieve)、更新(Update)和删除(Delete)

    我另一篇文 mongoose 5.x中的Promise和async/await
    提到,每一个Mongoose Query,接收的回调都遵循 callback(error, result)的模式。
    传了回调即意味着执行操作。如果执行时发生错误,返回的error 即是错误文档, result 则会是 null;若未出错,error 为 null,result 即是查询的结果。如果我们不传回调,用 async/await 的 方式 去获取返回,那返回值的内容等效于 result, error 就要靠我们用 catch 去捕获了。
    当成功返回数据(err 为 null),result 的格式取决于做的是什么操作:

    • 新增文档: Model.create() : ( Array|Object ) 成功创建的数据文档 / 文档数组

    • 查询单条文档: Model.findOne()Model.findById() :( result: Object ) 未查到符合条件的文档则是 null

    • 查询文档列表: Model.find() :( result: Array ) 符合filter条件的文档数组,没有符合条件的则是空数组[]

    • 查询文档数量: Model.countDocuments():( result: Number ) 没有符合条件的则是 0

    • 更新单个文档/文档列表:
      (1) Model.update()Model.updateMany()Model.updateOne():( result: Object )
      Object 参数说明:
      ok: Number (err 为 null 必然是1)
      n: Number (匹配filter条件的数量)
      nModified: Number: 被更新的文档数量。(如果没查到符合条件的数据,则是 0,也就是更新了0 条,不等于操作失败)。
      (2) Model.findOneAndUpdate()Model.findByIdAndUpdate():( result: Object ) 更新之前的文档(默认),如果在options传了new: true,则是成功更新之后的文档。没查到匹配的则是null

    • 删除单个/多个文档
      (1) Model.deleteMany()Model.deleteOne():( result: Object )
      Object 参数说明:
      ok: Number (err 为 null 必然是1)
      deletedCount: Number: 被删除的文档数量。(如果没查到符合条件的数据,则是 0,也就是删除了0 条,不等于操作失败)
      n: Number: 已删除文档的数量。等于deletedCount。
      (2) Model.findOneAndDelete()Model.findByIdAndDelete():( result: Object ) 查询到(被删)的这条文档,如果没符合条件的则是 null

    Models API 文档中有详细描述被传给回调函数的值。

    我们做增删改查操作都是通过Model方法,看过Middleware这一节就会明白,调用这些Api实际上是去触发一些更深层的方法,比如Document.save()Query.prototype.find()Model.create()Model.find()等等都是被包装过的快捷方式(语法糖)。(下面 中间件 和 Api返回值细则 部分会详细讲)

    使用示例

    同时我们也提到过因为Query返回的不是Promise,通过加上exec()便能返回完整的Promise,所以我们现在一般结合await获取 result(推荐):

    try {
      const doc = await MyModel.findOne({ country: 'Croatia' }).exec()
      // 如果没有符合 { country: 'Croatia' } 的数据,返回 `null`
      if(doc === null) {
        return res.json({
          code: 400,
          message: '没有符合条件的数据'
        })
      }
      res.json({
          code: 200,
          data: docs
          message: '查询成功'
        })
    } catch (err) {
      console.log(err)
    }
    

    也可使用回调拿 result:

    // 查找 name 含有 john 的数据,并且返回的列表中的对象只需要 "name" 和 "friends" 这两个字段
    MyModel.find({ name: /john/i }, 'name friends').exec((err, docs) => {
      if(err) return next(err)
      if(docs.length === 0) {
        return res.json({
          code: 400,
          message: '没有符合条件的数据'
        })
      }
      console.log(docs) 
    })
    

    关于中间件(这一部分主要为了加深对mongoose操作数据Api的理解,如果只是作为前端搭建简单的服务器了解即可

    中间件 (也叫 prepost 钩子) 是在异步函数执行时传入的控制函数。Mongoose 有四种中间件: document 中间件,model 中间件,aggregate 中间件,和 query 中间件。

    所有的中间件都支持 pre 和 post 钩子,它们的工作方式是怎样的呢:

    错误处理

    如果 pre 钩子出错,mongoose 将不会执行后面的函数。 Mongoose 会向回调函数传入 err 参数, 或者 reject 返回的 promise。

    schema.pre('save', function(next) {
      const err = new Error('something went wrong');
      // If you call `next()` with an argument, that argument is assumed to be
      // an error.
      next(err);
    });
    
    schema.pre('save', function() {
      // You can also return a promise that rejects
      return new Promise((resolve, reject) => {
        reject(new Error('something went wrong'));
      });
    });
    
    schema.pre('save', function() {
      // You can also throw a synchronous error
      throw new Error('something went wrong');
    });
    
    schema.pre('save', async function() {
      await Promise.resolve();
      // You can also throw an error in an `async` function
      throw new Error('something went wrong');
    });
    
    // save做的更改不会执行到 MongoDB,因为一个pre钩子(预处理钩子)出错了
    myDoc.save(function(err) {
      console.log(err.message); // 出错了
    });
    
    Post 中间件

    post 中间件在方法执行之后调用,这个时候每个 pre 中间件都已经完成。

    异步 Post 钩子

    如果你给回调函数传入两个参数,mongoose 会认为第二个参数是 next() 函数,你可以通过 next 触发下一个中间件

    // Takes 2 parameters: this is an asynchronous post hook
    schema.post('save', function(doc, next) {
      setTimeout(function() {
        console.log('post1');
        // Kick off the second post hook
        next();
      }, 10);
    });
    
    // Will not execute until the first middleware calls `next()`
    schema.post('save', function(doc, next) {
      console.log('post2');
      next();
    });
    
    Save/Validate Hooks

    save()方法会触发validate()钩子,因为mongoose有一个内置的pre('save')钩子会调用validate()。这意味着所有的pre('validate')post('validate')钩子都会在任何pre('save')钩子之前被调用。

    schema.pre('validate', function() {
      console.log('this gets printed first');
    });
    schema.post('validate', function() {
      console.log('this gets printed second');
    });
    schema.pre('save', function() {
      console.log('this gets printed third');
    });
    schema.post('save', function() {
      console.log('this gets printed fourth');
    });
    

    findAndUpdate() 与 Query 中间件注意事项

    Pre 和 post save() 钩子都不会在update(), findOneAndUpdate()等 Query 方法上执行。
    Query 中间件与 document 中间件有一个细微但重要的区别:在 document 中间件的this指的是正在被更新的文档。而query 中间件,mongoose不一定有一个对正在更新的文档的引用,所以它的this指向query对象。
    利用这一点,如果你想为每个updateOne()调用添加一个updatedAt时间戳,可以使用下面的pre钩子。

    schema.pre('updateOne', function() {
      // this指 query 对象
      this.set({ updatedAt: new Date() });
    });
    

    同样的,你不能在pre('updateOne')pre('findOneAndUpdate')query中间件中访问要被更新的文档。如果需要访问这个文档,则需要对该文档执行显式查询。如下:

    schema.pre('findOneAndUpdate', async function() {
      const docToUpdate = await this.model.findOne(this.getQuery());
      console.log(docToUpdate); // The document that `findOneAndUpdate()` will modify
    });
    

    Error Handling Middleware

    中间件的执行通常在出现错误并调用next(err)时停止。然而有一种特殊的post中间件称为“错误处理中间件”,它特定在错误发生时执行。错误处理中间件对于报告错误和提高错误消息的可读性非常有用。
    错误处理中间件比普通中间件多一个 error 参数,并且这个 error 作为第一个参数传入。 然后错误处理中间件可以让你自由地进行错误的后续处理。

    const schema = new Schema({
      name: {
        type: String,
        // Will trigger a MongoError with code 11000 when
        // you save a duplicate
        unique: true
      }
    });
    
    // Handler **must** take 3 parameters: the error that occurred, the document
    // in question, and the `next()` function
    schema.post('save', function(error, doc, next) {
      if (error.name === 'MongoError' && error.code === 11000) {
        next(new Error('There was a duplicate key error'));
      } else {
        next();
      }
    });
    
    // Will trigger the `post('save')` error handler
    Person.create([{ name: 'Axl Rose' }, { name: 'Axl Rose' }]);
    

    query 中间件也可以使用错误处理中间件。你可以定义一个 post update() 钩子, 它可以捕获 MongoDB 重复 key 错误。

    // The same E11000 error can occur when you call `update()`
    // This function **must** take 3 parameters. If you use the
    // `passRawResult` function, this function **must** take 4
    // parameters
    schema.post('update', function(error, res, next) {
      if (error.name === 'MongoError' && error.code === 11000) {
        next(new Error('There was a duplicate key error'));
      } else {
        next(); // The `update()` call will still error out.
      }
    });
    
    const people = [{ name: 'Axl Rose' }, { name: 'Slash' }];
    Person.create(people, function(error) {
      Person.update({ name: 'Slash' }, { $set: { name: 'Axl Rose' } }, function(error) {
        // `error.message` will be "There was a duplicate key error"
      });
    });
    

    错误处理中间件可以转换错误,但不能移除错误。只要没有调用next(error)把错误传递给后面的中间件去处理,这个中间件的调用仍然会出错。

    ————————————————————————————————————————

    *主要看这里*result 参数具体格式细则表【即 (err, result) 中的 result】

    新增数据

    实际触发的中间件方法 Document.prototype.save():如果该document的isNew参数是 true,则会在数据库中插入一个新文档。否则,只会发送一个updateOne操作对原数据进行修改。

    Model.create()

    docs : Array|Object (返回成功创建的数据文档/文档数组)
    触发的中间件: save()

    create()是向数据库保存一条或多条文档的快捷方式。MyModel.create(docs) 会对docs的每一条文档执行一次new MyModel(doc).save()

    查询数据

    Mongoose支持 MongoDB丰富的查询语法 。可以用 Model.find, findById, findOne, or where 静态方法来检索文档。

    (1) 查询符合条件的第一条数据

    Query.prototype.findOne()

    doc: Object (没有符合条件的是 null
    基于它的方法:Model.findOne()Model.findById()Model的一些方法返回Query对象,实质上调用的就是Query的原型方法「作用是声明一个操作命令」,result一致故而可以参考,下文不再重复说明

    (2) 查询符合条件的全部文档

    Query.prototype.find()

    docs: Array (没有符合条件的是 []
    基于它的方法:Model.find()

    (3) 查询符合条件的文档数量

    Query.prototype.countDocuments()

    count: Number (没有符合条件的是 0

    基于它的方法:Model.countDocuments()

    注⚠️:count() 已被弃用,请用countDocuments()替代

    更新数据

    更新数据有两种方式,先检索 findOne() 再保存 save() 通常是使用Mongoose更新文档的正确方式。通过save(),我们可以得到完整的验证和中间件。

    但当我们仅仅需要更新而不需要获取数据时,一些Query方法就很适合我们,比如 updateMany()
    update()updateMany()findOneAndUpdate()等不会执行save()中间件,因此也不会在修改数据库时先执行任何钩子或验证。如果需要完全成熟的验证,可以使用先检索文档再save()的传统方法。

    (1) 更新单条数据

    Query.prototype.findOneAndUpdate()

    doc: Object 默认是更新之前的文档对象,如果在options传了new: true,则返回成功更新之后的文档对象。没查到匹配的则是null

    发起的 mongodb命令:findAndModify 更新指令。
    基于它的方法:Model.findOneAndUpdate()Model.findByIdAndUpdate()

    Query.prototype.updateOne

    writeOpResult: Object 内含参数如下:
    ok: Number (err 为 null 必然是1)
    n: Number (匹配filter条件的数量)
    nModified: Number: 被更新的文档数量。(如果没查到符合条件的数据,则是 0,也就是更新了0 条,不等于操作失败)

    基于它的方法:Model.updateOne()

    { n: 1, nModified: 1, ok: 1 }
    

    只更新匹配filter的第一个文档。

    (2) 更新多条数据

    Query.prototype.update

    writeOpResult: Object 对象内容同 UpdateOne

    基于它的方法:Model.update()

    Query.prototype.updateMany

    writeOpResult: Object 对象内容同 UpdateOne

    基于它的方法:Model.updateMany()

    删除数据

    (1) 删除单条数据

    Query.prototype.deleteOne()

    mongooseDeleteResutl: Object,内含三个参数:
    ok: Number (err 为 null 必然是1)
    deletedCount: Number: 被删除的文档数量。(如果没查到符合条件的数据,则是 0,也就是删除了0 条,不等于操作失败)
    n: Number: 已删除文档的数量。等于deletedCount。

    这个方法调用 MongoDB driver's Collection#deleteOne() function.
    基于它的方法:Model.deleteOne()

    {ok: 1, deletedCount:1, n: 1} 
    
    Query.prototype.findOneAndDelete()

    doc: Object 查询到的(要被删的)的这条数据,如果没查到符合的则是 null
    需要返回被删文档数据的时候使用。

    发起的 MongoDB 命令: findOneAndDelete 指令
    基于它的方法:Model.findOneAndDelete()Model.findByIdAndDelete()

    (2) 删除多条数据

    Query.prototype.deleteMany()

    mongooseDeleteResult: Object 对象内容同 deleteOne

    这个方法调用MongoDB driver的Collection#deleteMany() function
    基于它的方法:Model.deleteMany()

    示例:

    const id = req.params.id.split(',')
    try {
      const response = await Model.deleteMany({ _id: { $in: id } }).exec()
      if(response.ok && response.deletedCount) {
        res.json({
          code: 200,
          message: '批量删除成功'
        })
      } else{
        res.json({
          code: 400,
          message: '删除失败'
        })
      }
    } catch (err) {
      next(err)
    }
    

    相关文章

      网友评论

          本文标题:mongoose Api CRUD操作返回结果总结梳理

          本文链接:https://www.haomeiwen.com/subject/naewcltx.html