Transactions in MongooseMongoose中的事务
Transactions事务 are new in MongoDB 4.0 and Mongoose 5.2.0. 是MongoDB 4.0和Mongoose 5.2.0中的新增功能。Transactions let you execute multiple operations in isolation and potentially undo all the operations if one of them fails. 事务允许您孤立地执行多个操作,如果其中一个操作失败,则可能会撤消所有操作。This guide will get you started using transactions with Mongoose.本指南将帮助您开始使用Mongoose的事务。
Getting Started with Transactions事务入门
If you haven't already, import mongoose:如果您还没有,请导入mongoose:
import mongoose from 'mongoose';
To create a transaction, you first need to create a session using Mongoose#startSession or Connection#startSession().要创建事务,首先需要使用Mongoose#startSession
或Connection#startSession()
创建会话。
// Using Mongoose's default connection
const session = await mongoose.startSession();
// Using custom connection
const db = await mongoose.createConnection(mongodbUri).asPromise();
const session = await db.startSession();
In practice, you should use either the 在实践中,您应该使用session.withTransaction()
helper or Mongoose's Connection#transaction()
function to run a transaction. session.withTransaction()
助手或Mongoose的Connection#transaction()
函数来运行事务。The session.withTransaction()
helper handles:session.withTransaction()
帮助程序处理:
Creating a transaction创建事务Committing the transaction if it succeeds如果事务成功,则提交事务Aborting the transaction if your operation throws如果您的操作引发,则中止事务Retrying in the event of a transient transaction error.在出现暂时事务错误时重试。
let session = null;
return Customer.createCollection().
then(() => Customer.startSession()).
// The `withTransaction()` function's first parameter is a function
// that returns a promise.
then(_session => {
session = _session;
return session.withTransaction(() => {
return Customer.create([{ name: 'Test' }], { session: session });
});
}).
then(() => Customer.countDocuments()).
then(count => assert.strictEqual(count, 1)).
then(() => session.endSession());
For more information on the 有关ClientSession#withTransaction()
function, please see the MongoDB Node.js driver docs.ClientSession#withTransaction()
函数的更多信息,请参阅MongoDB Node.js驱动程序文档。
Mongoose's Mongoose的Connection#transaction()
function is a wrapper around withTransaction()
that integrates Mongoose change tracking with transactions. Connection#transaction()
函数是一个withTransaction()
的包装器,它将Mongoose更改跟踪与事务集成在一起。For example, suppose you 例如,假设您在一个稍后失败的事务中save()
a document in a transaction that later fails. save()
一个文档。The changes in that document are not persisted to MongoDB. 该文档中的更改不会持久化到MongoDB中。The Connection#transaction()
function informs Mongoose change tracking that the save()
was rolled back, and marks all fields that were changed in the transaction as modified.Connection#transaction()
函数通知Mongoose更改跟踪save()
已回滚,并将事务中更改的所有字段标记为已修改。
const doc = new Person({ name: 'Will Riker' });
await db.transaction(async function setRank(session) {
doc.name = 'Captain';
await doc.save({ session });
doc.isNew; // false
// Throw an error to abort the transaction
throw new Error('Oops!');
}, { readPreference: 'primary' }).catch(() => {});
// true, `transaction()` reset the document's state because the
// transaction was aborted.
doc.isNew;
With Mongoose Documents and save()
使用Mongoose文档和save()
save()
If you get a Mongoose document from findOne() or find() using a session, the document will keep a reference to the session and use that session for save().如果您使用会话从findOne()
或find()
获得Mongoose文档,该文档将保留对该会话的引用,并使用该会话进行save()
。
To get/set the session associated with a given document, use doc.$session().要获取/设置与给定文档关联的会话,请使用doc.$session()
。
const User = db.model('User', new Schema({ name: String }));
let session = null;
return User.createCollection().
then(() => db.startSession()).
then(_session => {
session = _session;
return User.create({ name: 'foo' });
}).
then(() => {
session.startTransaction();
return User.findOne({ name: 'foo' }).session(session);
}).
then(user => {
// Getter/setter for the session associated with this document.
assert.ok(user.$session());
user.name = 'bar';
// By default, `save()` uses the associated session
return user.save();
}).
then(() => User.findOne({ name: 'bar' })).
// Won't find the doc because `save()` is part of an uncommitted transaction
then(doc => assert.ok(!doc)).
then(() => session.commitTransaction()).
then(() => session.endSession()).
then(() => User.findOne({ name: 'bar' })).
then(doc => assert.ok(doc));
With the Aggregation Framework使用聚合框架
The Model.aggregate()
function also supports transactions. Model.aggregate()
函数也支持事务。Mongoose aggregations have a Mongoose聚合有一个session()
helper that sets the session
option. session()
帮助程序,用于设置session
选项。Below is an example of executing an aggregation within a transaction.下面是在事务中执行聚合的示例。
const Event = db.model('Event', new Schema({ createdAt: Date }), 'Event');
let session = null;
return Event.createCollection().
then(() => db.startSession()).
then(_session => {
session = _session;
session.startTransaction();
return Event.insertMany([
{ createdAt: new Date('2018-06-01') },
{ createdAt: new Date('2018-06-02') },
{ createdAt: new Date('2017-06-01') },
{ createdAt: new Date('2017-05-31') }
], { session: session });
}).
then(() => Event.aggregate([
{
$group: {
_id: {
month: { $month: '$createdAt' },
year: { $year: '$createdAt' }
},
count: { $sum: 1 }
}
},
{ $sort: { count: -1, '_id.year': -1, '_id.month': -1 } }
]).session(session)).
then(res => assert.deepEqual(res, [
{ _id: { month: 6, year: 2018 }, count: 2 },
{ _id: { month: 6, year: 2017 }, count: 1 },
{ _id: { month: 5, year: 2017 }, count: 1 }
])).
then(() => session.commitTransaction()).
then(() => session.endSession());
Advanced Usage高级使用
Advanced users who want more fine-grained control over when they commit or abort transactions can use 高级用户如果希望在提交或中止事务时进行更细粒度的控制,可以使用session.startTransaction()
to start a transaction:session.startTransaction()
启动事务:
const Customer = db.model('Customer', new Schema({ name: String }));
let session = null;
return Customer.createCollection().
then(() => db.startSession()).
then(_session => {
session = _session;
// Start a transaction
session.startTransaction();
// This `create()` is part of the transaction because of the `session`
// option.
return Customer.create([{ name: 'Test' }], { session: session });
}).
// Transactions execute in isolation, so unless you pass a `session`
// to `findOne()` you won't see the document until the transaction
// is committed.
then(() => Customer.findOne({ name: 'Test' })).
then(doc => assert.ok(!doc)).
// This `findOne()` will return the doc, because passing the `session`
// means this `findOne()` will run as part of the transaction.
then(() => Customer.findOne({ name: 'Test' }).session(session)).
then(doc => assert.ok(doc)).
// Once the transaction is committed, the write operation becomes visible outside of the transaction.一旦事务被提交,写操作就可以在事务之外看到。
then(() => session.commitTransaction()).
then(() => Customer.findOne({ name: 'Test' })).
then(doc => assert.ok(doc)).
then(() => session.endSession());
You can also use 您也可以使用session.abortTransaction()
to abort a transaction:session.abortTransaction()
中止事务:
let session = null;
return Customer.createCollection().
then(() => Customer.startSession()).
then(_session => {
session = _session;
session.startTransaction();
return Customer.create([{ name: 'Test' }], { session: session });
}).
then(() => Customer.create([{ name: 'Test2' }], { session: session })).
then(() => session.abortTransaction()).
then(() => Customer.countDocuments()).
then(count => assert.strictEqual(count, 0)).
then(() => session.endSession());