乐闻世界logo
搜索文章和话题

Mongoose 中 save 和 update 有什么区别?

9 个月前提问
5 个月前修改
浏览次数130

7个答案

1
2
3
4
5
6
7

Mongoose 是一个 MongoDB 对象模型库,用于在 Node.js 环境中以对象映射文档(Object-Document Mapping,ODM)的方式操作 MongoDB 数据库。.save() 方法和 update() 方法都用于在数据库中持久化文档数据,但它们之间存在一些关键区别:

.save() 方法

  1. 创建或更新: .save() 通常用于保存一个新的文档实例或更新现有的文档。如果保存的文档实例具有 _id 字段并且在数据库中能找到对应的记录,那么它会执行更新操作。如果没有 _id 字段或者 _id 在数据库中找不到匹配的记录,则会创建一个新记录。

  2. 全文档操作: 当你使用 .save() 时,你通常是在操作整个文档。无论是创建新文档还是更新现有文档,你都会发送整个文档的数据到数据库。

  3. 中间件触发: .save() 方法会触发 Mongoose 的中间件(如 prepost 钩子)。这意味着在保存过程中,可以执行自定义逻辑(如密码加密、数据验证等)。

  4. 返回值: .save() 方法执行后,会返回被保存的文档对象。

  5. 示例:

    javascript
    const user = new UserModel({ name: 'Alice', email: 'alice@example.com' }); user.save(function(err, savedUser) { if (err) throw err; // savedUser 是保存后的文档对象 });

update() 方法

  1. 只用于更新: .update() 方法仅用于更新已存在的文档。它不能用于创建新文档。

  2. 部分文档操作: 当你使用 .update() 方法时,你可以只更新文档的某些部分,而不是发送整个文档。这通常用于性能优化,因为只传输需要更新的字段。

  3. 没有中间件触发: 使用 .update() 方法时,通常不会触发 Mongoose 中间件。如果需要在更新操作前后执行特定逻辑,可能需要手动处理。

  4. 返回值: .update() 方法执行后,返回的是一个包含操作结果的对象,如更新的文档数量,而不是更新后的文档对象。

  5. 示例:

    javascript
    UserModel.update({ _id: userId }, { $set: { name: 'Bob' } }, function(err, result) { if (err) throw err; // result 是操作结果对象,可以通过 result.nModified 获取被更新的文档数量 });

总结一下,.save() 用于创建新文档或替换整个文档,而 .update() 用于修改现有文档的部分字段。.save() 方法会触发中间件,返回保存的文档;.update() 方法不触发中间件,返回操作的结果。根据具体的应用场景和性能考虑,开发者可以选择最合适的方法进行数据库操作。### 应用场景比较

.save() 方法的应用场景:

  • 新建文档场景:当你有一个全新的文档并打算将其添加到数据库中时,可以使用 .save() 来实现。例如,当一个新用户注册到你的应用时,你需要创建一个新的用户文档。

  • 完整文档更新场景:如果你需要更新文档,并且更新涉及许多字段,或者你已经在应用层加载并可能修改了整个文档,那么使用 .save() 方法更新整个文档可能更为方便。

  • 需要中间件处理的场景:当你的保存逻辑需要触发 Mongoose 中间件,比如数据验证、自动设置时间戳、散列密码等操作时,.save() 方法是更好的选择。

.update() 方法的应用场景:

  • 部分更新场景:当你需要更新文档中的一个或几个字段,并且不需要加载整个文档时,.update() 是一个更高效的选择。这常见于需要快速响应的Web应用中。

  • 批量更新场景:当你需要更新多个文档且每个文档的更新都是相同的操作时,.update() 方法可以通过一次操作来更新所有匹配的文档,这通常比逐一加载文档并调用 .save() 更有效率。

  • 无需中间件场景:如果你不需要触发保存过程中的中间件,比如在执行一些批量操作或后台任务时,.update() 可以避免中间件的性能开销。

直接修改和替换文档

  • .save() 替换文档:当你使用 .save() 方法时,如果文档存在 _id 字段且该 _id 在数据库中找到了对应的记录,Mongoose 会替换掉原来的文档。这意味着,如果有些字段在新文档中没有指定,它们将从数据库中的文档里被移除。

  • .update() 修改字段:与 .save() 不同,.update() 方法默认仅修改指定的字段,而不会影响未指定的字段。对于需要保留其他字段内容的场合,这通常是更加安全和预期的行为。

性能考量

  • 性能优化:在大型应用中,.update() 方法对性能的影响通常小于 .save() 方法。特别是在进行部分字段更新时,.update() 方法不需要发送整个文档数据,也不需要加载文档到应用层,从而减少了网络传输和内存使用。

  • 原子操作.update() 方法允许使用 MongoDB 的原子更新操作符,如 $set, $inc, $push 等,这样可以确保更新操作的原子性,防止在并发场景下出现数据不一致的问题。

总结

根据你的需要,你可能会选择使用 .save().update()——或者是 Mongoose 提供的其他相关方法,如 .updateOne(), .updateMany(), .findOneAndUpdate() 等,这些方法提供了不同的功能和性能取舍。选择哪一种方法取决于你的具体需求,比如是否需要处理整个文档,是否需要触发中间件,是否关注性能优化,以及操作的原子性等因素。

2024年6月29日 12:07 回复

先说两个概念。您的应用程序是客户端,Mongodb 是服务器

主要区别在于,.save()您的客户端代码中已经有一个对象,或者在写回数据之前必须从服务器检索数据,并且您正在写回整个内容。

另一方面.update()不需要数据从服务器加载到客户端。所有交互都发生在服务器端,而不检索到客户端。因此,.update()当您向现有文档添加内容时,这种方式可以非常有效。

此外,还有一个multi参数.update()允许对多个符合查询条件的文档执行操作。

当用作调用时,便利方法中的一些东西你会失去.update(),但某些操作的好处是你必须承担的“权衡”。有关此内容以及可用选项的更多信息,请参阅文档

简而言之.save()是客户端接口,.update()是服务器端。

2024年6月29日 12:07 回复

一些差异:

  • 正如其他地方所指出的,它比其次update更有效,因为它避免了加载整个文档。find``save
  • Mongooseupdate会转换为 MongoDB update,但 Mongoosesave会转换为 MongoDB insert(用于新文档)或update.
  • 需要注意的是saveMongoose 在内部比较文档并仅发送实际更改的字段。这有利于原子性。
  • 默认情况下,验证不会运行update但可以启用。
  • 中间件 API(prepost钩子)是不同的。
2024年6月29日 12:07 回复

Mongoose 有一个有用的功能,称为中间件。有“前”和“后”中间件。中间件会在您执行“保存”时执行,但不会在“更新”期间执行。例如,如果您想在每次修改密码时对 User schema 中的密码进行哈希处理,则可以使用 pre 来执行此操作,如下所示。另一个有用的示例是为每个文档设置lastModified。该文档可以在http://mongoosejs.com/docs/middleware.html找到

shell
UserSchema.pre('save', function(next) { var user = this; // only hash the password if it has been modified (or is new) if (!user.isModified('password')) { console.log('password not modified'); return next(); } console.log('password modified'); // generate a salt bcrypt.genSalt(10, function(err, salt) { if (err) { return next(err); } // hash the password along with our new salt bcrypt.hash(user.password, salt, function(err, hash) { if (err) { return next(err); } // override the cleartext password with the hashed one user.password = hash; next(); }); }); });
2024年6月29日 12:07 回复

一个不容忽视的细节:并发性

正如前面提到的,在执行操作时doc.save(),您必须先将文档加载到内存中,然后对其进行修改,最后将doc.save()更改发送到 MongoDB 服务器。

当同时以这种方式编辑文档时就会出现问题:

  • A 加载文档 (v1)
  • B 加载文档 (v1)
  • B 保存对文档的更改(现在是 v2)
  • A 保存对过时 (v1) 文档的更改
  • A 会看到 Mongoose 抛出 VersionError,因为文档自上次从集合加载以来已发生更改

在执行像这样的原子操作时,并发不是问题Model.updateOne(),因为操作完全在 MongoDB 服务器中完成,MongoDB 服务器执行一定程度的**并发控制**。

因此,要小心!

2024年6月29日 12:07 回复

Mongoose 是一个 MongoDB 对象模型库,用于在 Node.js 环境中以对象映射文档(Object-Document Mapping,ODM)的方式操作 MongoDB 数据库。.save() 方法和 update() 方法都用于在数据库中持久化文档数据,但它们之间存在一些关键区别:

.save() 方法

  1. 创建或更新: .save() 通常用于保存一个新的文档实例或更新现有的文档。如果保存的文档实例具有 _id 字段并且在数据库中能找到对应的记录,那么它会执行更新操作。如果没有 _id 字段或者 _id 在数据库中找不到匹配的记录,则会创建一个新记录。

  2. 全文档操作: 当你使用 .save() 时,你通常是在操作整个文档。无论是创建新文档还是更新现有文档,你都会发送整个文档的数据到数据库。

  3. 中间件触发: .save() 方法会触发 Mongoose 的中间件(如 prepost 钩子)。这意味着在保存过程中,可以执行自定义逻辑(如密码加密、数据验证等)。

  4. 返回值: .save() 方法执行后,会返回被保存的文档对象。

  5. 示例:

    javascript
    const user = new UserModel({ name: 'Alice', email: 'alice@example.com' }); user.save(function(err, savedUser) { if (err) throw err; // savedUser 是保存后的文档对象 });

update() 方法

  1. 只用于更新: .update() 方法仅用于更新已存在的文档。它不能用于创建新文档。

  2. 部分文档操作: 当你使用 .update() 方法时,你可以只更新文档的某些部分,而不是发送整个文档。这通常用于性能优化,因为只传输需要更新的字段。

  3. 没有中间件触发: 使用 .update() 方法时,通常不会触发 Mongoose 中间件。如果需要在更新操作前后执行特定逻辑,可能需要手动处理。

  4. 返回值: .update() 方法执行后,返回的是一个包含操作结果的对象,如更新的文档数量,而不是更新后的文档对象。

  5. 示例:

    javascript
    UserModel.update({ _id: userId }, { $set: { name: 'Bob' } }, function(err, result) { if (err) throw err; // result 是操作结果对象,可以通过 result.nModified 获取被更新的文档数量 });

总结一下,.save() 用于创建新文档或替换整个文档,而 .update() 用于修改现有文档的部分字段。.save() 方法会触发中间件,返回保存的文档;.update() 方法不触发中间件,返回操作的结果。根据具体的应用场景和性能考虑,开发者可以选择最合适的方法进行数据库操作。

2024年6月29日 12:07 回复

Mongoose 是一个 MongoDB 对象模型库,用于在 Node.js 环境中以对象映射文档(Object-Document Mapping,ODM)的方式操作 MongoDB 数据库。.save() 方法和 update() 方法都用于在数据库中持久化文档数据,但它们之间存在一些关键区别:

.save() 方法

  1. 创建或更新: .save() 通常用于保存一个新的文档实例或更新现有的文档。如果保存的文档实例具有 _id 字段并且在数据库中能找到对应的记录,那么它会执行更新操作。如果没有 _id 字段或者 _id 在数据库中找不到匹配的记录,则会创建一个新记录。

  2. 全文档操作: 当你使用 .save() 时,你通常是在操作整个文档。无论是创建新文档还是更新现有文档,你都会发送整个文档的数据到数据库。

  3. 中间件触发: .save() 方法会触发 Mongoose 的中间件(如 prepost 钩子)。这意味着在保存过程中,可以执行自定义逻辑(如密码加密、数据验证等)。

  4. 返回值: .save() 方法执行后,会返回被保存的文档对象。

  5. 示例:

    javascript
    const user = new UserModel({ name: 'Alice', email: 'alice@example.com' }); user.save(function(err, savedUser) { if (err) throw err; // savedUser 是保存后的文档对象 });

update() 方法

  1. 只用于更新: .update() 方法仅用于更新已存在的文档。它不能用于创建新文档。

  2. 部分文档操作: 当你使用 .update() 方法时,你可以只更新文档的某些部分,而不是发送整个文档。这通常用于性能优化,因为只传输需要更新的字段。

  3. 没有中间件触发: 使用 .update() 方法时,通常不会触发 Mongoose 中间件。如果需要在更新操作前后执行特定逻辑,可能需要手动处理。

  4. 返回值: .update() 方法执行后,返回的是一个包含操作结果的对象,如更新的文档数量,而不是更新后的文档对象。

  5. 示例:

    javascript
    UserModel.update({ _id: userId }, { $set: { name: 'Bob' } }, function(err, result) { if (err) throw err; // result 是操作结果对象,可以通过 result.nModified 获取被更新的文档数量 });

总结一下,.save() 用于创建新文档或替换整个文档,而 .update() 用于修改现有文档的部分字段。.save() 方法会触发中间件,返回保存的文档;.update() 方法不触发中间件,返回操作的结果。根据具体的应用场景和性能考虑,开发者可以选择最合适的方法进行数据库操作。

应用场景比较

.save() 方法的应用场景:

  • 新建文档场景:当你有一个全新的文档并打算将其添加到数据库中时,可以使用 .save() 来实现。例如,当一个新用户注册到你的应用时,你需要创建一个新的用户文档。

  • 完整文档更新场景:如果你需要更新文档,并且更新涉及许多字段,或者你已经在应用层加载并可能修改了整个文档,那么使用 .save() 方法更新整个文档可能更为方便。

  • 需要中间件处理的场景:当你的保存逻辑需要触发 Mongoose 中间件,比如数据验证、自动设置时间戳、散列密码等操作时,.save() 方法是更好的选择。

.update() 方法的应用场景:

  • 部分更新场景:当你需要更新文档中的一个或几个字段,并且不需要加载整个文档时,.update() 是一个更高效的选择。这常见于需要快速响应的Web应用中。

  • 批量更新场景:当你需要更新多个文档且每个文档的更新都是相同的操作时,.update() 方法可以通过一次操作来更新所有匹配的文档,这通常比逐一加载文档并调用 .save() 更有效率。

  • 无需中间件场景:如果你不需要触发保存过程中的中间件,比如在执行一些批量操作或后台任务时,.update() 可以避免中间件的性能开销。

直接修改和替换文档

  • .save() 替换文档:当你使用 .save() 方法时,如果文档存在 _id 字段且该 _id 在数据库中找到了对应的记录,Mongoose 会替换掉原来的文档。这意味着,如果有些字段在新文档中没有指定,它们将从数据库中的文档里被移除。

  • .update() 修改字段:与 .save() 不同,.update() 方法默认仅修改指定的字段,而不会影响未指定的字段。对于需要保留其他字段内容的场合,这通常是更加安全和预期的行为。

性能考量

  • 性能优化:在大型应用中,.update() 方法对性能的影响通常小于 .save() 方法。特别是在进行部分字段更新时,.update() 方法不需要发送整个文档数据,也不需要加载文档到应用层,从而减少了网络传输和内存使用。

  • 原子操作.update() 方法允许使用 MongoDB 的原子更新操作符,如 $set, $inc, $push 等,这样可以确保更新操作的原子性,防止在并发场景下出现数据不一致的问题。

总结

根据你的需要,你可能会选择使用 .save().update()——或者是 Mongoose 提供的其他相关方法,如 .updateOne(), .updateMany(), .findOneAndUpdate() 等,这些方法提供了不同的功能和性能取舍。选择哪一种方法取决于你的具体需求,比如是否需要处理整个文档,是否需要触发中间件,是否关注性能优化,以及操作的原子性等因素。

2024年6月29日 12:07 回复

你的答案