MongoDB
 sql >> база данни >  >> NoSQL >> MongoDB

Как да използвам MongoDB транзакция с Mongoose?

Трябва да включите 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 с малки букви, тъй като това е името на ключа за обекта "опции", където се изисква за всички операции. Запазването на това в конвенцията с малки букви позволява да се използват и неща като присвояването на ES6 Object:

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
      }
    ]
  }
]


  1. Redis
  2.   
  3. MongoDB
  4.   
  5. Memcached
  6.   
  7. HBase
  8.   
  9. CouchDB
  1. Операции за запис на файлове в mongo скрипт?

  2. mongoDB/mongoose:уникален, ако не е нула

  3. Грешка в MongoDB:Не може да се използва повторно записване с limit=0

  4. Поддокументи на Mongoose срещу вложена схема

  5. Персонализирано каскадиране в Spring Data MongoDB