MongoDB
 sql >> Teknologi Basis Data >  >> NoSQL >> MongoDB

Bagaimana cara menggunakan transaksi MongoDB menggunakan Mongoose?

Anda harus menyertakan session dalam opsi untuk semua operasi baca/tulis yang aktif selama transaksi. Hanya dengan begitu mereka benar-benar diterapkan pada lingkup transaksi di mana Anda dapat mengembalikannya.

Sebagai daftar yang sedikit lebih lengkap, dan hanya menggunakan Order/OrderItems yang lebih klasik pemodelan yang seharusnya cukup akrab bagi kebanyakan orang dengan beberapa pengalaman transaksi relasional:

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

})()

Jadi saya biasanya akan merekomendasikan memanggil variabel session dalam huruf kecil, karena ini adalah nama kunci untuk objek "opsi" yang diperlukan pada semua operasi. Menyimpan ini dalam konvensi huruf kecil memungkinkan untuk menggunakan hal-hal seperti penetapan Objek ES6 juga:

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

...

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

Juga dokumentasi luwak pada transaksi sedikit menyesatkan, atau setidaknya bisa lebih deskriptif. Apa yang dimaksud dengan db dalam contoh sebenarnya adalah contoh Koneksi Mongoose, dan bukan Db underlying yang mendasarinya atau bahkan mongoose impor global karena beberapa orang mungkin salah menafsirkan ini. Perhatikan dalam daftar dan kutipan di atas ini diperoleh dari mongoose.connect() dan harus disimpan dalam kode Anda sebagai sesuatu yang dapat Anda akses dari impor bersama.

Sebagai alternatif, Anda bahkan dapat mengambil ini dalam kode modular melalui mongoose.connection properti, kapan saja setelah koneksi telah dibuat. Ini biasanya aman di dalam hal-hal seperti penangan rute server dan sejenisnya karena akan ada koneksi database pada saat kode itu dipanggil.

Kode juga menunjukkan session penggunaan dalam metode model yang berbeda:

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

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

Semua find() metode berbasis dan update() atau insert() dan delete() metode berbasis semua memiliki "blok opsi" terakhir di mana kunci dan nilai sesi ini diharapkan. save() satu-satunya argumen metode ini adalah blok opsi ini. Inilah yang memberi tahu MongoDB untuk menerapkan tindakan ini ke transaksi saat ini pada sesi yang direferensikan itu.

Dengan cara yang hampir sama, sebelum transaksi dilakukan, permintaan apa pun untuk find() atau serupa yang tidak menentukan session opsi tidak melihat status data saat transaksi sedang berlangsung. Status data yang dimodifikasi hanya tersedia untuk operasi lain setelah transaksi selesai. Perhatikan bahwa ini memiliki efek pada penulisan seperti yang tercakup dalam dokumentasi.

Ketika "aborsi" dikeluarkan:

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

await session.abortTransaction();

Setiap operasi pada transaksi aktif dihapus dari status dan tidak diterapkan. Karena itu, mereka tidak terlihat oleh operasi yang dihasilkan setelahnya. Dalam contoh di sini nilai dalam dokumen bertambah dan akan menunjukkan nilai yang diambil dari 5 pada sesi saat ini. Namun setelah session.abortTransaction() status dokumen sebelumnya dikembalikan. Perhatikan bahwa konteks global apa pun yang tidak membaca data pada sesi yang sama, tidak melihat status itu berubah kecuali di-commit.

Itu harus memberikan gambaran umum. Ada lebih banyak kerumitan yang dapat ditambahkan untuk menangani berbagai tingkat kegagalan penulisan dan percobaan ulang, tetapi hal itu telah dibahas secara luas dalam dokumentasi dan banyak contoh, atau dapat dijawab dengan pertanyaan yang lebih spesifik.

Keluaran

Untuk referensi, output dari daftar yang disertakan ditampilkan di sini:

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. Sekarang Tersedia:Instans MongoDB yang Dihosting Sepenuhnya di AWS

  2. Cara menggunakan janji luwak - luwak

  3. mongodb:menemukan nilai numerik tertinggi dari sebuah kolom

  4. validasi kustom luwak menggunakan 2 bidang

  5. simpan luwak vs sisipkan vs buat