sql >> Database >  >> NoSQL >> MongoDB

Hoe MongoDB-transactie te gebruiken met Mongoose?

U moet de session . opnemen binnen de opties voor alle lees-/schrijfbewerkingen die actief zijn tijdens een transactie. Alleen dan worden ze daadwerkelijk toegepast op het transactiebereik waar u ze kunt terugdraaien.

Als een wat completere lijst, en gewoon met behulp van de meer klassieke Order/OrderItems modellering die voor de meeste mensen met enige ervaring met relationele transacties redelijk bekend zou moeten zijn:

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

})()

Dus ik zou over het algemeen aanraden om de variabele session aan te roepen in kleine letters, aangezien dit de naam is van de sleutel voor het "options"-object waar deze bij alle bewerkingen vereist is. Als u dit in de conventie voor kleine letters houdt, kunt u ook dingen als de ES6-objecttoewijzing gebruiken:

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

...

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

Ook de mangoestdocumentatie over transacties is een beetje misleidend, of het zou in ieder geval meer beschrijvend kunnen zijn. Waar het naar verwijst als db in de voorbeelden is eigenlijk de Mongoose Connection-instantie, en niet de onderliggende Db of zelfs de mongoose wereldwijde import, aangezien sommigen dit verkeerd kunnen interpreteren. Opmerking in de lijst en het bovenstaande fragment is verkregen van mongoose.connect() en moet binnen uw code worden bewaard als iets waartoe u toegang hebt via een gedeelde import.

Als alternatief kun je dit zelfs in modulaire code pakken via de mongoose.connection eigendom, op elk moment na er is een verbinding tot stand gebracht. Dit is meestal veilig binnen zaken zoals serverroute-handlers en dergelijke, aangezien er een databaseverbinding zal zijn tegen de tijd dat die code wordt aangeroepen.

De code demonstreert ook de session gebruik in de verschillende modelmethoden:

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

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

Alle find() gebaseerde methoden en de update() of insert() en delete() gebaseerde methoden hebben allemaal een laatste "options block" waar deze sessiesleutel en waarde worden verwacht. De save() het enige argument van de methode is dit optieblok. Dit is wat MongoDB vertelt om deze acties toe te passen op de huidige transactie in die sessie waarnaar wordt verwezen.

Op vrijwel dezelfde manier, voordat een transactie wordt uitgevoerd, alle verzoeken voor een find() of iets dergelijks die niet specificeren dat session optie ziet de status van de gegevens niet terwijl die transactie aan de gang is. De gewijzigde gegevensstatus is alleen beschikbaar voor andere bewerkingen zodra de transactie is voltooid. Merk op dat dit gevolgen heeft voor het schrijven zoals beschreven in de documentatie.

Wanneer een "afbreken" wordt gegeven:

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

await session.abortTransaction();

Alle bewerkingen op de actieve transactie worden uit de status verwijderd en niet toegepast. Als zodanig zijn ze niet zichtbaar voor resulterende bewerkingen achteraf. In het voorbeeld hier wordt de waarde in het document verhoogd en wordt een opgehaalde waarde van 5 weergegeven. op de huidige sessie. Maar na session.abortTransaction() de vorige staat van het document wordt teruggezet. Merk op dat elke globale context die geen gegevens in dezelfde sessie aan het lezen was, die statuswijziging niet ziet tenzij deze is vastgelegd.

Dat zou het algemene overzicht moeten geven. Er is meer complexiteit die kan worden toegevoegd om verschillende niveaus van schrijffouten en nieuwe pogingen aan te pakken, maar dat wordt al uitgebreid behandeld in documentatie en veel voorbeelden, of kan worden beantwoord op een meer specifieke vraag.

Uitvoer

Ter referentie, de output van de bijgevoegde lijst wordt hier getoond:

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. hoe een .csv-gegevensbestand in de Redis-database te importeren

  2. MongoDB $oid versus ObjectId

  3. Een redis-listener maken - mogelijk in php?

  4. Veld toevoegen dat niet in schema staat met mangoest