MongoDB
 sql >> Cơ Sở Dữ Liệu >  >> NoSQL >> MongoDB

Làm thế nào để sử dụng giao dịch MongoDB bằng Mongoose?

Bạn cần bao gồm session trong các tùy chọn cho tất cả các hoạt động đọc / ghi đang hoạt động trong một giao dịch. Chỉ khi đó, chúng mới thực sự được áp dụng cho phạm vi giao dịch mà bạn có thể khôi phục chúng.

Như một danh sách đầy đủ hơn một chút và chỉ sử dụng Order/OrderItems cổ điển hơn lập mô hình vốn đã khá quen thuộc với hầu hết mọi người có một số kinh nghiệm về giao dịch quan hệ:

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

})()

Vì vậy, tôi thường khuyên bạn nên gọi biến session bằng chữ thường, vì đây là tên của khóa cho đối tượng "tùy chọn", nơi nó được yêu cầu trên tất cả các hoạt động. Giữ điều này trong quy ước chữ thường cũng cho phép sử dụng những thứ như gán Đối tượng ES6:

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

...

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

Ngoài ra, tài liệu mongoose về các giao dịch có một chút sai lệch, hoặc ít nhất nó có thể mang tính mô tả nhiều hơn. Nó gọi là gì db trong các ví dụ thực sự là phiên bản Mongoose Connection chứ không phải Db cơ bản hoặc thậm chí là mongoose nhập khẩu toàn cầu vì một số có thể hiểu sai điều này. Lưu ý trong danh sách và đoạn trích ở trên, nội dung này được lấy từ mongoose.connect() và phải được giữ trong mã của bạn như một thứ mà bạn có thể truy cập từ một lần nhập được chia sẻ.

Ngoài ra, bạn thậm chí có thể lấy nó trong mã mô-đun thông qua mongoose.connection tài sản, bất kỳ lúc nào sau một kết nối đã được thiết lập. Điều này thường an toàn bên trong những thứ như trình xử lý tuyến máy chủ và những thứ tương tự vì sẽ có kết nối cơ sở dữ liệu vào thời điểm mã đó được gọi.

Mã cũng thể hiện session sử dụng trong các phương pháp mô hình khác nhau:

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

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

Tất cả find() các phương thức dựa trên và update() hoặc insert()delete() tất cả các phương pháp dựa trên đều có một "khối tùy chọn" cuối cùng, nơi khóa và giá trị của phiên này được mong đợi. save() đối số duy nhất của phương thức là khối tùy chọn này. Đây là những gì yêu cầu MongoDB áp dụng các hành động này cho giao dịch hiện tại trên phiên tham chiếu đó.

Theo cách tương tự, trước khi giao dịch được thực hiện bất kỳ yêu cầu nào đối với find() hoặc tương tự không chỉ định session đó tùy chọn không thấy trạng thái của dữ liệu trong khi giao dịch đó đang diễn ra. Trạng thái dữ liệu đã sửa đổi chỉ có sẵn cho các hoạt động khác sau khi giao dịch hoàn tất. Lưu ý rằng điều này có ảnh hưởng đến việc ghi như được đề cập trong tài liệu.

Khi "hủy bỏ" được đưa ra:

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

await session.abortTransaction();

Mọi thao tác trên giao dịch đang hoạt động đều bị xóa khỏi trạng thái và không được áp dụng. Vì vậy, chúng không được hiển thị cho các hoạt động kết quả sau đó. Trong ví dụ ở đây, giá trị trong tài liệu được tăng dần và sẽ hiển thị giá trị được truy xuất là 5 vào phiên hiện tại. Tuy nhiên sau session.abortTransaction() trạng thái trước đó của tài liệu được hoàn nguyên. Lưu ý rằng bất kỳ ngữ cảnh toàn cầu nào không đọc dữ liệu trên cùng một phiên, sẽ không thấy trạng thái đó thay đổi trừ khi được cam kết.

Điều đó sẽ cung cấp cho các tổng quan chung. Có thể thêm phức tạp hơn để xử lý các mức độ lỗi ghi và thử lại khác nhau, nhưng điều đó đã được đề cập rộng rãi trong tài liệu và nhiều mẫu hoặc có thể được trả lời cho một câu hỏi cụ thể hơn.

Đầu ra

Để tham khảo, đầu ra của danh sách bao gồm được hiển thị ở đây:

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. Cách chạy các lệnh mongodb thô từ pymongo

  2. Liệt kê tất cả các bộ sưu tập trong cơ sở dữ liệu mongo trong tập lệnh nodejs

  3. có một cuộc gọi lại lỗi kết nối mongoose không

  4. Cách kết nối mongodb từ xa với pymongo

  5. Cách quản lý tài liệu trong MongoDB