sql >> Database teknologi >  >> NoSQL >> MongoDB

Hvordan bruger man MongoDB-transaktion ved hjælp af Mongoose?

Du skal inkludere session inden for mulighederne for alle læse/skrive-operationer, der er aktive under en transaktion. Først derefter anvendes de faktisk på transaktionsomfanget, hvor du er i stand til at rulle dem tilbage.

Som en lidt mere komplet liste, og blot ved at bruge den mere klassiske Order/OrderItems modellering, som burde være ret velkendt for de fleste mennesker med nogle relationelle transaktionserfaringer:

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

})()

Så jeg vil generelt anbefale at kalde variablen session med små bogstaver, da dette er navnet på nøglen til "options"-objektet, hvor det er påkrævet ved alle operationer. Ved at holde dette i små bogstaver kan du også bruge ting som ES6-objekttildelingen:

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

...

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

Også mongoose-dokumentationen om transaktioner er lidt misvisende, eller den kunne i det mindste være mere beskrivende. Hvad det refererer til som db i eksemplerne er faktisk Mongoose Connection-forekomsten og ikke den underliggende Db eller endda mongoose global import, da nogle måske misfortolker dette. Bemærk i listen og ovenstående uddrag, at dette er hentet fra mongoose.connect() og bør opbevares i din kode som noget, du kan få adgang til fra en delt import.

Alternativt kan du endda få fat i dette i modulær kode via mongoose.connection ejendom, til enhver tid efter en forbindelse er etableret. Dette er normalt sikkert inde i ting såsom serverrutehandlere og lignende, da der vil være en databaseforbindelse, når koden kaldes.

Koden demonstrerer også session brug i de forskellige modelmetoder:

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

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

Alle find() baseret metoder og update() eller insert() og delete() baseret metoder har alle en sidste "optionsblok", hvor denne sessionsnøgle og værdi forventes. save() metodens eneste argument er denne option blok. Dette er, hvad der fortæller MongoDB at anvende disse handlinger på den aktuelle transaktion på den refererede session.

På nogenlunde samme måde, før en transaktion udføres, er der nogen anmodninger om en find() eller lignende, som ikke specificerer den session mulighed ikke se status for dataene, mens transaktionen er i gang. Den ændrede datatilstand er kun tilgængelig for andre operationer, når transaktionen er fuldført. Bemærk, at dette har indvirkning på skrivninger som beskrevet i dokumentationen.

Når en "abort" udstedes:

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

await session.abortTransaction();

Eventuelle handlinger på den aktive transaktion fjernes fra tilstand og anvendes ikke. Som sådan er de ikke synlige for efterfølgende operationer. I eksemplet her er værdien i dokumentet øget og vil vise en hentet værdi på 5 på den aktuelle session. Men efter session.abortTransaction() den tidligere tilstand af dokumentet vendes tilbage. Bemærk, at enhver global kontekst, der ikke læste data på samme session, ikke kan se denne tilstand ændres, medmindre den er forpligtet.

Det burde give det generelle overblik. Der er mere kompleksitet, der kan tilføjes for at håndtere forskellige niveauer af skrivefejl og genforsøg, men det er allerede omfattende dækket i dokumentation og mange eksempler, eller kan besvares på et mere specifikt spørgsmål.

Output

Til reference er outputtet af den inkluderede liste vist her:

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. Importer en CSV-fil til MongoDB med mongoimport

  2. Sådan finder du tilfældig rekord i Mongoose

  3. PHP Mongo Fejl ved læsning fra socket

  4. MongoDB $minut