MongoDB 4.0 開始提供了事務支持,mongoose 也提供了相應的實現,不過目前的寫法仍是比較繁瑣。
咱們看一下 mongoose 給出的 demohtml
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));
這個 demo 暴露了兩個問題:node
因此 akajs 提供了一個事務的註解來簡化這個處理流程。git
import * as mongoose from 'mongoose' import {Schema} from 'mongoose' import * as assert from 'assert' import {Transactional, getSession} from './decorators/Transactional' mongoose.connect('mongodb://localhost:27017,localhost:27018,localhost:27019/test?replicaSet=rs', {useNewUrlParser: true}) mongoose.set('debug', true) let db = mongoose.connection const Customer = db.model('Customer', new Schema({name: String})) class ClassA { @Transactional() async main (key) { await new Customer({name: 'ClassA'}).save({session: getSession()}) const doc1 = await Customer.findOne({name: 'ClassA'}) assert.ok(!doc1) await new ClassB().step2() return key } } class ClassB { async step2 () { const doc2 = await Customer.findOne({name: 'ClassA'}).session(getSession()) assert.ok(doc2) await Customer.remove({}).session(getSession()) } } new ClassA().main('aaa').then((res) => { console.log('res', res) mongoose.disconnect(console.log) }).catch(console.error)
解析:github
try { const value = await originalMethod.apply(this, [...args]) // Since the mutations ran without an error, commit the transaction. await session.commitTransaction() // Return any value returned by `mutations` to make this function as transparent as possible. return value } catch (error) { // Abort the transaction as an error has occurred in the mutations above. await session.abortTransaction() // Rethrow the error to be caught by the caller. throw error } finally { // End the previous session. session.endSession() }
你對此介意的話,請不要在生產環境使用。mongodb
注意 mongodb 的事務必須在複製集上使用,在開發環境啓動 mongodb 複製集,推薦使用 run-rsnpm
固然,在每個須要 Session 的地方調用 getSession() 方法仍是稍顯累贅,咱們能夠經過 wrap mongoose 的各個方法,來實現自動注入 session。api
例如把 mongoose 的 findOne 方法替換爲session
let originFindOne = mongoose.findOne mongoose.findOne = () => { originFindOne().session(getSession()) }
可是工做量有些多,暫時沒時間作。app