sql >> データベース >  >> NoSQL >> MongoDB

Mongooseを使用してMongoDBトランザクションを使用するにはどうすればよいですか?

    sessionを含める必要があります トランザクション中にアクティブになるすべての読み取り/書き込み操作のオプション内。そうして初めて、それらは実際にトランザクションスコープに適用され、そこでロールバックできます。

    もう少し完全なリストとして、より古典的なOrder/OrderItemsを使用するだけです リレーショナルトランザクションの経験を持つほとんどの人にかなり馴染みのあるモデリング:

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

    したがって、通常、変数sessionを呼び出すことをお勧めします。 小文字。これは、すべての操作で必要となる「options」オブジェクトのキーの名前であるためです。これを小文字の規則に保つと、ES6オブジェクトの割り当てなども使用できるようになります。

    const conn = await mongoose.connect(uri, opts);
    
    ...
    
    let session = await conn.startSession();
    session.startTransaction();
    

    また、トランザクションに関するマングースのドキュメントは少し誤解を招く可能性があります。または、少なくともそれはより説明的である可能性があります。 dbと呼ばれるもの 例では、実際にはMongoose Connectionインスタンスであり、基になるDbではありません。 またはmongoose 一部の人はこれを誤解する可能性があるため、グローバルインポート。リストと上記の抜粋では、これはmongoose.connect()から取得されていることに注意してください。 共有インポートからアクセスできるものとして、コード内に保持する必要があります。

    または、mongoose.connectionを介してモジュラーコードでこれを取得することもできます。 プロパティ、いつでも 接続が確立されました。これは通常、コードが呼び出されるまでにデータベース接続が確立されるため、サーバールートハンドラーなどの内部では安全です。

    このコードは、sessionも示しています さまざまなモデルメソッドでの使用法:

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

    すべてのfind() ベースのメソッドとupdate() またはinsert() およびdelete() ベースのメソッドはすべて、このセッションキーと値が期待される最後の「オプションブロック」を持っています。 save() メソッドの唯一の引数は、このオプションブロックです。これは、MongoDBに、参照されているセッションの現在のトランザクションにこれらのアクションを適用するように指示するものです。

    同様に、トランザクションがコミットされる前に、find()のリクエストがあります。 またはそのsessionを指定しない類似のもの オプションは、そのトランザクションの進行中はデータの状態を表示しません。変更されたデータ状態は、トランザクションが完了すると他の操作でのみ使用できます。ドキュメントで説明されているように、これは書き込みに影響を与えることに注意してください。

    「中止」が発行された場合:

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

    アクティブなトランザクションに対する操作はすべて状態から削除され、適用されません。そのため、後で結果として生じる操作には表示されません。この例では、ドキュメントの値がインクリメントされ、取得した5の値が表示されます。 現在のセッションで。ただし、session.abortTransaction()の後 ドキュメントの以前の状態が元に戻されます。同じセッションでデータを読み取っていなかったグローバルコンテキストは、コミットされない限り、その状態の変化を認識しないことに注意してください。

    これで一般的な概要がわかります。さまざまなレベルの書き込みの失敗と再試行を処理するために追加できる複雑さがありますが、それはすでにドキュメントと多くのサンプルで広範囲にカバーされているか、より具体的な質問に答えることができます。

    出力

    参考までに、含まれているリストの出力をここに示します:

    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. MongoDBドキュメント内の一意の埋め込み/ネストされたオブジェクトのリストを取得する

    2. MongoDB配列で重複する値を見つける

    3. Mongodbは重複エントリを回避します

    4. MongoDBでの遅いクエリの処理