Anda harus menyertakan session
dalam opsi untuk semua operasi baca/tulis yang aktif selama transaksi. Hanya dengan begitu mereka benar-benar diterapkan pada lingkup transaksi di mana Anda dapat mengembalikannya.
Sebagai daftar yang sedikit lebih lengkap, dan hanya menggunakan Order/OrderItems
yang lebih klasik pemodelan yang seharusnya cukup akrab bagi kebanyakan orang dengan beberapa pengalaman transaksi relasional:
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()
}
})()
Jadi saya biasanya akan merekomendasikan memanggil variabel session
dalam huruf kecil, karena ini adalah nama kunci untuk objek "opsi" yang diperlukan pada semua operasi. Menyimpan ini dalam konvensi huruf kecil memungkinkan untuk menggunakan hal-hal seperti penetapan Objek ES6 juga:
const conn = await mongoose.connect(uri, opts);
...
let session = await conn.startSession();
session.startTransaction();
Juga dokumentasi luwak pada transaksi sedikit menyesatkan, atau setidaknya bisa lebih deskriptif. Apa yang dimaksud dengan db
dalam contoh sebenarnya adalah contoh Koneksi Mongoose, dan bukan Db
underlying yang mendasarinya atau bahkan mongoose
impor global karena beberapa orang mungkin salah menafsirkan ini. Perhatikan dalam daftar dan kutipan di atas ini diperoleh dari mongoose.connect()
dan harus disimpan dalam kode Anda sebagai sesuatu yang dapat Anda akses dari impor bersama.
Sebagai alternatif, Anda bahkan dapat mengambil ini dalam kode modular melalui mongoose.connection
properti, kapan saja setelah koneksi telah dibuat. Ini biasanya aman di dalam hal-hal seperti penangan rute server dan sejenisnya karena akan ada koneksi database pada saat kode itu dipanggil.
Kode juga menunjukkan session
penggunaan dalam metode model yang berbeda:
let [order, other] = await Order.insertMany([
{ name: 'Bill' },
{ name: 'Ted' }
], { session });
let fred = new Order({ name: 'Fred' });
await fred.save({ session });
Semua find()
metode berbasis dan update()
atau insert()
dan delete()
metode berbasis semua memiliki "blok opsi" terakhir di mana kunci dan nilai sesi ini diharapkan. save()
satu-satunya argumen metode ini adalah blok opsi ini. Inilah yang memberi tahu MongoDB untuk menerapkan tindakan ini ke transaksi saat ini pada sesi yang direferensikan itu.
Dengan cara yang hampir sama, sebelum transaksi dilakukan, permintaan apa pun untuk find()
atau serupa yang tidak menentukan session
opsi tidak melihat status data saat transaksi sedang berlangsung. Status data yang dimodifikasi hanya tersedia untuk operasi lain setelah transaksi selesai. Perhatikan bahwa ini memiliki efek pada penulisan seperti yang tercakup dalam dokumentasi.
Ketika "aborsi" dikeluarkan:
// Update and abort
let result2 = await OrderItems.findOneAndUpdate(
{ order: order._id, itemName: 'Milk' },
{ $inc: { price: 1 } },
{ 'new': true, session }
);
log(result2);
await session.abortTransaction();
Setiap operasi pada transaksi aktif dihapus dari status dan tidak diterapkan. Karena itu, mereka tidak terlihat oleh operasi yang dihasilkan setelahnya. Dalam contoh di sini nilai dalam dokumen bertambah dan akan menunjukkan nilai yang diambil dari 5
pada sesi saat ini. Namun setelah session.abortTransaction()
status dokumen sebelumnya dikembalikan. Perhatikan bahwa konteks global apa pun yang tidak membaca data pada sesi yang sama, tidak melihat status itu berubah kecuali di-commit.
Itu harus memberikan gambaran umum. Ada lebih banyak kerumitan yang dapat ditambahkan untuk menangani berbagai tingkat kegagalan penulisan dan percobaan ulang, tetapi hal itu telah dibahas secara luas dalam dokumentasi dan banyak contoh, atau dapat dijawab dengan pertanyaan yang lebih spesifik.
Keluaran
Untuk referensi, output dari daftar yang disertakan ditampilkan di sini:
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
}
]
}
]