sql >> Databáze >  >> NoSQL >> MongoDB

Jak používat transakci MongoDB pomocí Mongoose?

Musíte zahrnout session v rámci možností pro všechny operace čtení/zápisu, které jsou aktivní během transakce. Teprve poté se skutečně použijí na rozsah transakce, kde je můžete vrátit zpět.

Jako o něco úplnější výpis a jen pomocí klasičtějšího Order/OrderItems modelování, které by mělo být dobře známé většině lidí se zkušenostmi s relačními transakcemi:

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

})()

Obecně bych tedy doporučil volat proměnnou session malými písmeny, protože toto je název klíče pro objekt "options", kde je vyžadován u všech operací. Zachování této konvence s malými písmeny umožňuje používat také věci, jako je přiřazení objektu ES6:

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

...

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

Také mongoose dokumentace o transakcích je trochu zavádějící, nebo by alespoň mohla být více popisná. To, čemu se říká db v příkladech je ve skutečnosti instance Mongoose Connection, nikoli základní Db nebo dokonce mongoose globální import, protože to někteří mohou mylně interpretovat. Všimněte si, že ve výpisu a výše uvedeném úryvku je to získáno z mongoose.connect() a měl by být uložen ve vašem kódu jako něco, k čemu máte přístup ze sdíleného importu.

Alternativně to můžete dokonce uchopit v modulárním kódu přes mongoose.connection vlastnictví, kdykoli po bylo navázáno spojení. To je obvykle bezpečné uvnitř věcí, jako jsou obslužné nástroje směrování serveru a podobně, protože v době, kdy je zavolán kód, bude k dispozici připojení k databázi.

Kód také demonstruje session použití v různých modelových metodách:

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

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

Všechny find() založené metody a update() nebo insert() a delete() založené metody mají všechny poslední "blok možností", kde se očekává tento klíč relace a hodnota. save() Jediným argumentem metody je tento blok voleb. To je to, co říká MongoDB, aby použil tyto akce na aktuální transakci v odkazované relaci.

V podstatě stejným způsobem, než je transakce potvrzena, jakékoli požadavky na find() nebo podobné, které neurčují danou session možnost nezobrazovat stav dat, zatímco transakce probíhá. Upravený stav dat je dostupný pro ostatní operace až po dokončení transakce. Všimněte si, že to má vliv na zápisy, jak je popsáno v dokumentaci.

Když je vydáno "přerušení":

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

await session.abortTransaction();

Veškeré operace s aktivní transakcí jsou odstraněny ze stavu a nejsou aplikovány. Jako takové nejsou následně viditelné pro výsledné operace. V tomto příkladu je hodnota v dokumentu zvýšena a zobrazí načtenou hodnotu 5 na aktuální relaci. Nicméně po session.abortTransaction() vrátí se předchozí stav dokumentu. Všimněte si, že jakýkoli globální kontext, který nečetl data ve stejné relaci, nevidí změnu stavu, pokud není potvrzen.

To by mělo poskytnout všeobecný přehled. Existuje více složitosti, která může být přidána ke zvládnutí různých úrovní selhání zápisu a opakování, ale to je již rozsáhle pokryto v dokumentaci a mnoha ukázkách nebo lze odpovědět na konkrétnější otázku.

Výstup

Pro referenci je výstup zahrnutého výpisu zobrazen zde:

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. Rails 3:jak používat aktivní záznam a mongoid zároveň

  2. kolik celkových nebo maximálních připojení je dostupných na serveru Redis?

  3. Odlišný příkaz MongoDB

  4. Nejlepší postupy pro zabezpečení MongoDB