MongoDB
 sql >> Baza danych >  >> NoSQL >> MongoDB

Mangusta | Oprogramowanie pośredniczące | Operacje wycofywania wykonywane przez zaczepy pre/post, gdy zostanie zgłoszony błąd

TLDR; Oprogramowanie pośredniczące Mongoose nie zostało zaprojektowane do tego celu.

Ta metoda wstawiania transakcji jest w rzeczywistości łataniem funkcjonalności oprogramowania pośredniego i zasadniczo tworzysz interfejs API całkowicie oddzielony od mongoose oprogramowanie pośredniczące.

Lepiej byłoby odwrócić logikę zapytania usuwającego w osobnej funkcji.

Proste i zamierzone rozwiązanie

Pozwól, aby metoda obsługi transakcji działała magicznie i utwórz osobną metodę usuwania dla swojego modelu nadrzędnego. Mongoose wraps mongodb.ClientSession.prototype.withTransaction z mongoose.Connection.prototype.transaction i nie musimy nawet tworzyć instancji ani zarządzać sesją! Spójrz na różnicę między długością tego a tamtego poniżej. I oszczędzasz umysłowy ból głowy związany z zapamiętywaniem elementów wewnętrznych tego oprogramowania pośredniczącego kosztem jednej oddzielnej funkcji.


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!
}

Małe rozszerzenie

Innym sposobem, aby to ułatwić, jest zarejestrowanie oprogramowania pośredniczącego, które po prostu dziedziczy sesję iff _ zapytanie ma jeden zarejestrowany. Może zgłosić błąd, jeśli transakcja nie została rozpoczęta.

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

Ryzykowne i złożone rozwiązanie

To działa i jest całkowicie, strasznie skomplikowane. Niepolecane. Pewnego dnia prawdopodobnie się zepsuje, ponieważ opiera się na zawiłościach interfejsu API mangusty. Nie wiem, dlaczego to zakodowałem, proszę nie uwzględniać tego w swoich projektach .

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. Tworzenie baz danych w Pythonie i MongoDB

  2. Przechowuj dane lokalizacji w dokumencie Mongodb

  3. Nie można uruchomić MongoDB jako usługi

  4. Zmień kształt tablicy przechowywanej w kolekcji i eksportuj do CSV

  5. Jak usunąć TTL z kolekcji MongoDB?