MongoDB
 sql >> Baza danych >  >> NoSQL >> MongoDB

Jak korzystać z transakcji MongoDB za pomocą Mongoose?

Musisz dołączyć session w ramach opcji dla wszystkich operacji odczytu/zapisu, które są aktywne podczas transakcji. Dopiero wtedy są faktycznie stosowane do zakresu transakcji, w którym można je wycofać.

Jako nieco bardziej kompletna lista i tylko przy użyciu bardziej klasycznych Order/OrderItems modelowanie, które powinno być dobrze znane większości osób z pewnym doświadczeniem w transakcjach relacyjnych:

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()
  }

})()

Więc ogólnie polecam wywołanie zmiennej session małymi literami, ponieważ jest to nazwa klucza dla obiektu „opcje”, w którym jest on wymagany we wszystkich operacjach. Utrzymanie tego w konwencji małych liter pozwala również na użycie takich rzeczy, jak przypisanie obiektu ES6:

const conn = await mongoose.connect(uri, opts);

...

let session = await conn.startSession();
session.startTransaction();

Również dokumentacja dotycząca mangusty dotycząca transakcji jest trochę myląca, a przynajmniej może być bardziej opisowa. Do czego odnosi się jako db w przykładach jest w rzeczywistości instancją Mongoose Connection, a nie bazowym Db a nawet mongoose globalny import, ponieważ niektórzy mogą to błędnie zinterpretować. Uwaga na liście i powyższym fragmencie jest to uzyskiwane z mongoose.connect() i powinien być przechowywany w kodzie jako coś, do czego można uzyskać dostęp z udostępnionego importu.

Alternatywnie możesz nawet pobrać to w kodzie modułowym za pomocą mongoose.connection właściwość, w dowolnym momencie po połączenie zostało nawiązane. Jest to zwykle bezpieczne wewnątrz elementów, takich jak programy obsługi tras serwera i tym podobne, ponieważ do czasu wywołania kodu będzie połączenie z bazą danych.

Kod demonstruje również session zastosowanie w różnych metodach modelowych:

let [order, other] = await Order.insertMany([
  { name: 'Bill' },
  { name: 'Ted' }
], { session });

let fred = new Order({ name: 'Fred' });
await fred.save({ session });

Wszystkie find() oparte na metodach i update() lub insert() i delete() Wszystkie metody oparte mają końcowy „blok opcji”, w którym oczekiwany jest ten klucz sesji i wartość. save() jedynym argumentem metody jest ten blok opcji. To właśnie mówi MongoDB, aby zastosować te działania do bieżącej transakcji w tej sesji, do której się odwołuje.

W podobny sposób, zanim transakcja zostanie zatwierdzona, wszelkie żądania find() lub podobne, które nie określają, że session opcja nie wyświetlaj stanu danych, gdy transakcja jest w toku. Zmodyfikowany stan danych jest dostępny tylko dla innych operacji po zakończeniu transakcji. Zauważ, że ma to wpływ na zapisy opisane w dokumentacji.

Po wydaniu „przerwania”:

// Update and abort
let result2 = await OrderItems.findOneAndUpdate(
  { order: order._id, itemName: 'Milk' },
  { $inc: { price: 1 } },
  { 'new': true, session }
);
log(result2);

await session.abortTransaction();

Wszelkie operacje na aktywnej transakcji są usuwane ze stanu i nie są stosowane. Jako takie nie są widoczne dla późniejszych operacji. W tym przykładzie wartość w dokumencie jest zwiększana i pokaże pobraną wartość 5 w bieżącej sesji. Jednak po session.abortTransaction() poprzedni stan dokumentu zostaje przywrócony. Zwróć uwagę, że żaden kontekst globalny, który nie odczytywał danych w tej samej sesji, nie widzi zmiany stanu, dopóki nie zostanie zatwierdzony.

To powinno dać ogólny przegląd. Istnieje więcej złożoności, które można dodać, aby poradzić sobie z różnymi poziomami błędów zapisu i ponownych prób, ale jest to już obszernie omówione w dokumentacji i wielu przykładach lub można uzyskać odpowiedź na bardziej szczegółowe pytanie.

Wyjście

Dla porównania, dane wyjściowe dołączonej listy są pokazane tutaj:

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. Uruchomienie PHP Nie można załadować biblioteki dynamicznej php_mongo.dll

  2. MongoDB $dateToParts

  3. Jak uruchomić surowe polecenia mongodb z pymongo

  4. Sprawdź, czy każdy element w tablicy pasuje do warunku

  5. Mongodb i MAMP