TLDR;マングースミドルウェアはこのために設計されていません。
トランザクションを挿入するこの方法は、実際にはミドルウェア機能にパッチを適用しており、基本的にmongoose
とは完全に別のAPIを作成しています。 ミドルウェア。
別の関数で削除クエリのロジックを逆にする方がよいでしょう。
シンプルで意図されたソリューション
トランザクション処理メソッドがその魔法を実行できるようにし、親モデル用に別のremoveメソッドを作成します。 Mongooseはmongodb.ClientSession.prototype.withTransaction
をラップします mongoose.Connection.prototype.transaction
を使用 また、セッションをインスタンス化または管理する必要もありません。これと以下の長さの違いを見てください。 そして、1つの別個の機能を犠牲にして、そのミドルウェアの内部を覚えるという精神的な頭痛の種を軽減します。
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!
}
小さな拡張機能
これを簡単にするもう1つの方法は、セッションを iff継承するだけのミドルウェアを登録することです。 _クエリに1つ登録されています。トランザクションが開始されていない場合は、エラーがスローされる可能性があります。
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 });
});
}
危険で複雑なソリューション
これは機能し、完全に、恐ろしく複雑です。推奨されません。マングースAPIの複雑さに依存しているため、いつか壊れてしまう可能性があります。なぜこれをコーディングしたのかわかりません。プロジェクトに含めないでください 。
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);
});