如何在 Mongoose 中使用 findOneAndUpdate()
Mongoose 中的 findOneAndUpdate()
函數 有廣泛的應用情境。 您應該盡可能使用 save()
來更新文件,以獲得更好的 驗證 和 中介軟體 支援。然而,在某些情況下,您需要使用 findOneAndUpdate()
。在本教學中,您將了解如何使用 findOneAndUpdate()
,並了解何時需要使用它。
開始使用
顧名思義,findOneAndUpdate()
會找到符合給定 filter
的第一個文件,套用 update
,並返回該文件。 findOneAndUpdate()
函數具有以下簽名:
function findOneAndUpdate(filter, update, options) {}
預設情況下,findOneAndUpdate()
會返回 update
套用 **之前** 的文件。在以下範例中,doc
最初只有 name
和 _id
屬性。 findOneAndUpdate()
會新增 age
屬性,但 findOneAndUpdate()
的結果 **沒有** age
屬性。
const Character = mongoose.model('Character', new mongoose.Schema({
name: String,
age: Number
}));
const _id = new mongoose.Types.ObjectId('0'.repeat(24));
let doc = await Character.create({ _id, name: 'Jean-Luc Picard' });
doc; // { name: 'Jean-Luc Picard', _id: ObjectId('000000000000000000000000') }
const filter = { name: 'Jean-Luc Picard' };
const update = { age: 59 };
// The result of `findOneAndUpdate()` is the document _before_ `update` was applied
doc = await Character.findOneAndUpdate(filter, update);
doc; // { name: 'Jean-Luc Picard', _id: ObjectId('000000000000000000000000') }
doc = await Character.findOne(filter);
doc.age; // 59
您應該將 new
選項設定為 true
,以返回 update
套用 **之後** 的文件。
const filter = { name: 'Jean-Luc Picard' };
const update = { age: 59 };
// `doc` is the document _after_ `update` was applied because of
// `new: true`
const doc = await Character.findOneAndUpdate(filter, update, {
new: true
});
doc.name; // 'Jean-Luc Picard'
doc.age; // 59
Mongoose 的 findOneAndUpdate()
與 MongoDB Node.js 驅動程式的 findOneAndUpdate()
略有不同,因為它返回的是文件本身,而不是 結果物件。
作為 new
選項的替代方案,您也可以使用 returnOriginal
選項。 returnOriginal: false
等同於 new: true
。 returnOriginal
選項的存在是為了與 MongoDB Node.js 驅動程式的 findOneAndUpdate()
保持一致,後者具有相同的選項。
const filter = { name: 'Jean-Luc Picard' };
const update = { age: 59 };
// `doc` is the document _after_ `update` was applied because of
// `returnOriginal: false`
const doc = await Character.findOneAndUpdate(filter, update, {
returnOriginal: false
});
doc.name; // 'Jean-Luc Picard'
doc.age; // 59
原子更新
除了 未索引的 upsert 之外,findOneAndUpdate()
是原子的。這表示您可以假設文件在 MongoDB 找到文件和更新文件之間不會發生變更,除非 您正在執行 upsert。
例如,如果您使用 save()
來更新文件,則文件可能會在您使用 findOne()
載入文件和使用 save()
儲存文件之間,在 MongoDB 中發生變更,如下所示。對於許多使用情境,save()
的競爭條件不是問題。但是,如果您需要,可以使用 findOneAndUpdate()
(或 交易)來解決這個問題。
const filter = { name: 'Jean-Luc Picard' };
const update = { age: 59 };
let doc = await Character.findOne({ name: 'Jean-Luc Picard' });
// Document changed in MongoDB, but not in Mongoose
await Character.updateOne(filter, { name: 'Will Riker' });
// This will update `doc` age to `59`, even though the doc changed.
doc.age = update.age;
await doc.save();
doc = await Character.findOne();
doc.name; // Will Riker
doc.age; // 59
Upsert
使用 upsert
選項,您可以將 findOneAndUpdate()
用作尋找並 upsert 操作。如果找到符合 filter
的文件,upsert 的行為就像正常的 findOneAndUpdate()
。但是,如果沒有文件符合 filter
,MongoDB 會將 filter
和 update
組合在一起插入一個文件,如下所示。
const filter = { name: 'Will Riker' };
const update = { age: 29 };
await Character.countDocuments(filter); // 0
const doc = await Character.findOneAndUpdate(filter, update, {
new: true,
upsert: true // Make this update into an upsert
});
doc.name; // Will Riker
doc.age; // 29
includeResultMetadata
選項
Mongoose 預設會轉換 findOneAndUpdate()
的結果:它會返回更新後的文件。這使得難以檢查文件是否已執行 upsert。為了取得更新後的文件並檢查 MongoDB 是否在同一個操作中執行了新的文件 upsert,您可以設定 includeResultMetadata
旗標,讓 Mongoose 返回來自 MongoDB 的原始結果。
const filter = { name: 'Will Riker' };
const update = { age: 29 };
await Character.countDocuments(filter); // 0
const res = await Character.findOneAndUpdate(filter, update, {
new: true,
upsert: true,
// Return additional properties about the operation, not just the document
includeResultMetadata: true
});
res.value instanceof Character; // true
// The below property will be `false` if MongoDB upserted a new
// document, and `true` if MongoDB updated an existing object.
res.lastErrorObject.updatedExisting; // false
以下是上述範例中 res
物件的樣子:
{ lastErrorObject:
{ n: 1,
updatedExisting: false,
upserted: 5e6a9e5ec6e44398ae2ac16a },
value:
{ _id: 5e6a9e5ec6e44398ae2ac16a,
name: 'Will Riker',
__v: 0,
age: 29 },
ok: 1 }
更新辨別器鍵
Mongoose 預設會阻止使用 findOneAndUpdate()
更新 辨別器鍵。例如,假設您有以下辨別器模型。
const eventSchema = new mongoose.Schema({ time: Date });
const Event = db.model('Event', eventSchema);
const ClickedLinkEvent = Event.discriminator(
'ClickedLink',
new mongoose.Schema({ url: String })
);
const SignedUpEvent = Event.discriminator(
'SignedUp',
new mongoose.Schema({ username: String })
);
如果設定了 __t
,Mongoose 會從 update
參數中移除 __t
(預設辨別器鍵)。這是為了防止意外更新辨別器鍵;例如,如果您將不受信任的使用者輸入傳遞給 update
參數。但是,您可以透過將 overwriteDiscriminatorKey
選項設定為 true
,來告訴 Mongoose 允許更新辨別器鍵,如下所示。
let event = new ClickedLinkEvent({ time: Date.now(), url: 'google.com' });
await event.save();
event = await ClickedLinkEvent.findByIdAndUpdate(
event._id,
{ __t: 'SignedUp' },
{ overwriteDiscriminatorKey: true, new: true }
);
event.__t; // 'SignedUp', updated discriminator key