session
を含める必要があります トランザクション中にアクティブになるすべての読み取り/書き込み操作のオプション内。そうして初めて、それらは実際にトランザクションスコープに適用され、そこでロールバックできます。
もう少し完全なリストとして、より古典的なOrder/OrderItems
を使用するだけです リレーショナルトランザクションの経験を持つほとんどの人にかなり馴染みのあるモデリング:
const { Schema } = mongoose = require('mongoose');
// URI including the name of the replicaSet connecting to
const uri = 'mongodb://localhost:27017/trandemo?replicaSet=fresh';
const opts = { useNewUrlParser: true };
// sensible defaults
mongoose.Promise = global.Promise;
mongoose.set('debug', true);
mongoose.set('useFindAndModify', false);
mongoose.set('useCreateIndex', true);
// schema defs
const orderSchema = new Schema({
name: String
});
const orderItemsSchema = new Schema({
order: { type: Schema.Types.ObjectId, ref: 'Order' },
itemName: String,
price: Number
});
const Order = mongoose.model('Order', orderSchema);
const OrderItems = mongoose.model('OrderItems', orderItemsSchema);
// log helper
const log = data => console.log(JSON.stringify(data, undefined, 2));
// main
(async function() {
try {
const conn = await mongoose.connect(uri, opts);
// clean models
await Promise.all(
Object.entries(conn.models).map(([k,m]) => m.deleteMany())
)
let session = await conn.startSession();
session.startTransaction();
// Collections must exist in transactions
await Promise.all(
Object.entries(conn.models).map(([k,m]) => m.createCollection())
);
let [order, other] = await Order.insertMany([
{ name: 'Bill' },
{ name: 'Ted' }
], { session });
let fred = new Order({ name: 'Fred' });
await fred.save({ session });
let items = await OrderItems.insertMany(
[
{ order: order._id, itemName: 'Cheese', price: 1 },
{ order: order._id, itemName: 'Bread', price: 2 },
{ order: order._id, itemName: 'Milk', price: 3 }
],
{ session }
);
// update an item
let result1 = await OrderItems.updateOne(
{ order: order._id, itemName: 'Milk' },
{ $inc: { price: 1 } },
{ session }
);
log(result1);
// commit
await session.commitTransaction();
// start another
session.startTransaction();
// Update and abort
let result2 = await OrderItems.findOneAndUpdate(
{ order: order._id, itemName: 'Milk' },
{ $inc: { price: 1 } },
{ 'new': true, session }
);
log(result2);
await session.abortTransaction();
/*
* $lookup join - expect Milk to be price: 4
*
*/
let joined = await Order.aggregate([
{ '$match': { _id: order._id } },
{ '$lookup': {
'from': OrderItems.collection.name,
'foreignField': 'order',
'localField': '_id',
'as': 'orderitems'
}}
]);
log(joined);
} catch(e) {
console.error(e)
} finally {
mongoose.disconnect()
}
})()
したがって、通常、変数session
を呼び出すことをお勧めします。 小文字。これは、すべての操作で必要となる「options」オブジェクトのキーの名前であるためです。これを小文字の規則に保つと、ES6オブジェクトの割り当てなども使用できるようになります。
const conn = await mongoose.connect(uri, opts);
...
let session = await conn.startSession();
session.startTransaction();
また、トランザクションに関するマングースのドキュメントは少し誤解を招く可能性があります。または、少なくともそれはより説明的である可能性があります。 db
と呼ばれるもの 例では、実際にはMongoose Connectionインスタンスであり、基になるDb
ではありません。 またはmongoose
一部の人はこれを誤解する可能性があるため、グローバルインポート。リストと上記の抜粋では、これはmongoose.connect()
から取得されていることに注意してください。 共有インポートからアクセスできるものとして、コード内に保持する必要があります。
または、mongoose.connection
を介してモジュラーコードでこれを取得することもできます。 プロパティ、いつでも後 接続が確立されました。これは通常、コードが呼び出されるまでにデータベース接続が確立されるため、サーバールートハンドラーなどの内部では安全です。
このコードは、session
も示しています さまざまなモデルメソッドでの使用法:
let [order, other] = await Order.insertMany([
{ name: 'Bill' },
{ name: 'Ted' }
], { session });
let fred = new Order({ name: 'Fred' });
await fred.save({ session });
すべてのfind()
ベースのメソッドとupdate()
またはinsert()
およびdelete()
ベースのメソッドはすべて、このセッションキーと値が期待される最後の「オプションブロック」を持っています。 save()
メソッドの唯一の引数は、このオプションブロックです。これは、MongoDBに、参照されているセッションの現在のトランザクションにこれらのアクションを適用するように指示するものです。
同様に、トランザクションがコミットされる前に、find()
のリクエストがあります。 またはそのsession
を指定しない類似のもの オプションは、そのトランザクションの進行中はデータの状態を表示しません。変更されたデータ状態は、トランザクションが完了すると他の操作でのみ使用できます。ドキュメントで説明されているように、これは書き込みに影響を与えることに注意してください。
「中止」が発行された場合:
// Update and abort
let result2 = await OrderItems.findOneAndUpdate(
{ order: order._id, itemName: 'Milk' },
{ $inc: { price: 1 } },
{ 'new': true, session }
);
log(result2);
await session.abortTransaction();
アクティブなトランザクションに対する操作はすべて状態から削除され、適用されません。そのため、後で結果として生じる操作には表示されません。この例では、ドキュメントの値がインクリメントされ、取得した5
の値が表示されます。 現在のセッションで。ただし、session.abortTransaction()
の後 ドキュメントの以前の状態が元に戻されます。同じセッションでデータを読み取っていなかったグローバルコンテキストは、コミットされない限り、その状態の変化を認識しないことに注意してください。
これで一般的な概要がわかります。さまざまなレベルの書き込みの失敗と再試行を処理するために追加できる複雑さがありますが、それはすでにドキュメントと多くのサンプルで広範囲にカバーされているか、より具体的な質問に答えることができます。
出力
参考までに、含まれているリストの出力をここに示します:
Mongoose: orders.deleteMany({}, {})
Mongoose: orderitems.deleteMany({}, {})
Mongoose: orders.insertMany([ { _id: 5bf775986c7c1a61d12137dd, name: 'Bill', __v: 0 }, { _id: 5bf775986c7c1a61d12137de, name: 'Ted', __v: 0 } ], { session: ClientSession("80f827fe077044c8b6c0547b34605cb2") })
Mongoose: orders.insertOne({ _id: ObjectId("5bf775986c7c1a61d12137df"), name: 'Fred', __v: 0 }, { session: ClientSession("80f827fe077044c8b6c0547b34605cb2") })
Mongoose: orderitems.insertMany([ { _id: 5bf775986c7c1a61d12137e0, order: 5bf775986c7c1a61d12137dd, itemName: 'Cheese', price: 1, __v: 0 }, { _id: 5bf775986c7c1a61d12137e1, order: 5bf775986c7c1a61d12137dd, itemName: 'Bread', price: 2, __v: 0 }, { _id: 5bf775986c7c1a61d12137e2, order: 5bf775986c7c1a61d12137dd, itemName: 'Milk', price: 3, __v: 0 } ], { session: ClientSession("80f827fe077044c8b6c0547b34605cb2") })
Mongoose: orderitems.updateOne({ order: ObjectId("5bf775986c7c1a61d12137dd"), itemName: 'Milk' }, { '$inc': { price: 1 } }, { session: ClientSession("80f827fe077044c8b6c0547b34605cb2") })
{
"n": 1,
"nModified": 1,
"opTime": {
"ts": "6626894672394452998",
"t": 139
},
"electionId": "7fffffff000000000000008b",
"ok": 1,
"operationTime": "6626894672394452998",
"$clusterTime": {
"clusterTime": "6626894672394452998",
"signature": {
"hash": "AAAAAAAAAAAAAAAAAAAAAAAAAAA=",
"keyId": 0
}
}
}
Mongoose: orderitems.findOneAndUpdate({ order: ObjectId("5bf775986c7c1a61d12137dd"), itemName: 'Milk' }, { '$inc': { price: 1 } }, { session: ClientSession("80f827fe077044c8b6c0547b34605cb2"), upsert: false, remove: false, projection: {}, returnOriginal: false })
{
"_id": "5bf775986c7c1a61d12137e2",
"order": "5bf775986c7c1a61d12137dd",
"itemName": "Milk",
"price": 5,
"__v": 0
}
Mongoose: orders.aggregate([ { '$match': { _id: 5bf775986c7c1a61d12137dd } }, { '$lookup': { from: 'orderitems', foreignField: 'order', localField: '_id', as: 'orderitems' } } ], {})
[
{
"_id": "5bf775986c7c1a61d12137dd",
"name": "Bill",
"__v": 0,
"orderitems": [
{
"_id": "5bf775986c7c1a61d12137e0",
"order": "5bf775986c7c1a61d12137dd",
"itemName": "Cheese",
"price": 1,
"__v": 0
},
{
"_id": "5bf775986c7c1a61d12137e1",
"order": "5bf775986c7c1a61d12137dd",
"itemName": "Bread",
"price": 2,
"__v": 0
},
{
"_id": "5bf775986c7c1a61d12137e2",
"order": "5bf775986c7c1a61d12137dd",
"itemName": "Milk",
"price": 4,
"__v": 0
}
]
}
]