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

luwak | Perangkat Tengah | Operasi rollback dilakukan oleh pra/pasca kait ketika kesalahan dilemparkan

TLDR; Middleware luwak tidak dirancang untuk ini.

Metode penyisipan transaksi ini sebenarnya menambal fungsionalitas middleware, dan pada dasarnya Anda membuat api yang sepenuhnya terpisah dari mongoose perangkat tengah.

Apa yang akan lebih baik adalah membalikkan logika untuk kueri penghapusan Anda dalam fungsi terpisah.

Solusi Sederhana &Ditujukan

Izinkan metode penanganan transaksi melakukan keajaibannya, dan buat metode penghapusan terpisah untuk model induk Anda. Luwak membungkus mongodb.ClientSession.prototype.withTransaction dengan mongoose.Connection.prototype.transaction dan kita bahkan tidak perlu membuat instance atau mengelola sesi! Lihatlah perbedaan antara panjang ini dan itu di bawah ini. Dan Anda dapat menghemat sakit kepala mental karena mengingat internal middleware tersebut dengan mengorbankan satu fungsi terpisah.


const parentSchema = new mongoose.Schema({
    name: String,
    children: [{ type: mongoose.Schema.Types.ObjectId, ref: "Child" }],
});

const childSchema = new mongoose.Schema({
    name: String,
    parent: { type: mongoose.Schema.Types.ObjectId, ref: "Parent" },
});

// Assume `parent` is a parent document here
async function fullRemoveParent(parent) {
    // The document's connection
    const db = parent.db;

    // This handles everything with the transaction for us, including retries
    // session, commits, aborts, etc.
    await db.transaction(async function (session) {
        // Make sure to associate all actions with the session
        await parent.remove({ session });
        await db
            .model("Child")
            .deleteMany({ _id: { $in: parent.children } })
            .session(session);
    });

    // And done!
}

Ekstensi Kecil

Cara lain untuk mempermudah ini, adalah dengan mendaftarkan middleware yang hanya mewarisi sesi iff _ kueri memiliki satu yang terdaftar. Mungkin menimbulkan kesalahan jika transaksi belum dimulai.

const parentSchema = new mongoose.Schema({
    name: String,
    children: [{ type: mongoose.Schema.Types.ObjectId, ref: "Child" }],
});

const childSchema = new mongoose.Schema({
    name: String,
    parent: { type: mongoose.Schema.Types.ObjectId, ref: "Parent" },
});

parentSchema.pre("remove", async function () {
    // Look how easy!! Just make sure to pass a transactional 
    // session to the removal
    await this.db
        .model("Child")
        .deleteMany({ _id: { $in: parent.children } })
        .session(this.$session());

    // // If you want to: throw an error/warning if you forgot to add a session
    // // and transaction
    // if(!this.$session() || !this.$session().inTransaction()) {
    //    throw new Error("HEY YOU FORGOT A TRANSACTION.");
    // }
});

// Assume `parent` is a parent document here
async function fullRemoveParent(parent) {
    db.transaction(async function(session) {
        await parent.remove({ session });
    });
}

Solusi Berisiko &Kompleks

Ini bekerja, dan benar-benar, sangat kompleks. Tidak direkomendasikan. Kemungkinan akan rusak suatu hari nanti karena bergantung pada seluk-beluk API luwak. Saya tidak tahu mengapa saya membuat kode ini, tolong jangan sertakan dalam proyek Anda .

import mongoose from "mongoose";
import mongodb from "mongodb";

const parentSchema = new mongoose.Schema({
    name: String,
    children: [{ type: mongoose.Schema.Types.ObjectId, ref: "Child" }],
});

const childSchema = new mongoose.Schema({
    name: String,
    parent: { type: mongoose.Schema.Types.ObjectId, ref: "Parent" },
});

// Choose a transaction timeout
const TRANSACTION_TIMEOUT = 120000; // milliseconds

// No need for next() callback if using an async function.
parentSchema.pre("remove", async function () {
    // `this` refers to the document, not the query
    let session = this.$session();

    // Check if this op is already part of a session, and start one if not.
    if (!session) {
        // `this.db` refers to the documents's connection.
        session = await this.db.startSession();

        // Set the document's associated session.
        this.$session(session);

        // Note if you created the session, so post can clean it up.
        this.$locals.localSession = true;

        //
    }

    // Check if already in transaction.
    if (!session.inTransaction()) {
        await session.startTransaction();

        // Note if you created transaction.
        this.$locals.localTransaction = true;

        // If you want a timeout
        this.$locals.startTime = new Date();
    }

    // Let's assume that we need to remove all parent references in the
    // children. (just add session-associated ops to extend this)
    await this.db
        .model("Child") // Child model of this connection
        .updateMany(
            { _id: { $in: this.children } },
            { $unset: { parent: true } }
        )
        .session(session);
});

parentSchema.post("remove", async function (parent) {
    if (this.$locals.localTransaction) {
        // Here, there may be an error when we commit, so we need to check if it
        // is a 'retryable' error, then retry if so.
        try {
            await this.$session().commitTransaction();
        } catch (err) {
            if (
                err instanceof mongodb.MongoError &&
                err.hasErrorLabel("TransientTransactionError") &&
                new Date() - this.$locals.startTime < TRANSACTION_TIMEOUT
            ) {
                await parent.remove({ session: this.$session() });
            } else {
                throw err;
            }
        }
    }

    if (this.$locals.localSession) {
        await this.$session().endSession();
        this.$session(null);
    }
});

// Specific error handling middleware if its really time to abort (clean up
// the injections)
parentSchema.post("remove", async function (err, doc, next) {
    if (this.$locals.localTransaction) {
        await this.$session().abortTransaction();
    }

    if (this.$locals.localSession) {
        await this.$session().endSession();
        this.$session(null);
    }

    next(err);
});




  1. Redis
  2.   
  3. MongoDB
  4.   
  5. Memcached
  6.   
  7. HBase
  8.   
  9. CouchDB
  1. MongoDB:Temukan objek dengan nama bidang yang dimulai dengan

  2. Junit/Fongo:Cara menggunakan Fongo di unit test untuk memeriksa NotNull

  3. Perintah gagal dengan kesalahan 168 (InvalidPipelineOperator):'Ekspresi tidak dikenal '$match'

  4. Apakah MongoDB ChangeStream ResumeToken unik secara global?

  5. Bagaimana cara mengamankan MongoDB dengan nama pengguna dan kata sandi