Mongoose pre中间件实现级联删除时如何通过事务回滚数据
Mongoose pre中间件实现事务级联删除方案
不要在pre('remove')中间件内部单独开启事务,事务生命周期需要绑定最外层的业务操作入口,通过参数透传会话的方式让所有级联操作加入同一事务,才能实现全链路的提交/回滚统一管控,具体实现步骤如下:
1. 外层业务入口开启事务,透传会话
所有触发文档删除的业务逻辑,统一在入口层创建会话、启动事务,执行文档删除操作时将session作为参数传入,该参数会自动透传到关联的Mongoose中间件上下文中。
const mongoose = require('mongoose'); // 业务层删除入口示例 async function deleteTargetDoc(docId) { const session = await mongoose.startSession(); session.startTransaction(); try { // 查询待删除文档时就绑定会话 const targetDoc = await TargetModel.findById(docId).session(session); if (!targetDoc) throw new Error('待删除文档不存在'); // 执行文档remove操作,session会自动传递给中间件 await targetDoc.remove({ session }); // 全流程无异常则提交事务 await session.commitTransaction(); } catch (err) { // 任意环节报错直接回滚所有变更 await session.abortTransaction(); throw err; } finally { // 最终关闭会话 await session.endSession(); } }
2. pre remove中间件读取会话,绑定所有级联操作
在级联删除的pre中间件中,通过this.getOptions()即可获取到外层透传的session,所有关联的增删改操作都绑定该session,就会被纳入同一个事务管控,不会出现部分删除无法回滚的问题。
// Schema层pre remove中间件示例 targetSchema.pre('remove', async function() { // 读取操作参数中透传的会话 const { session } = this.getOptions(); // 所有级联操作必须传入session,才会加入当前事务 // 示例:删除关联的子文档 await RelatedModel.deleteMany( { targetId: this._id }, { session } ); // 示例:清理其他集合中对当前文档的引用 await OtherModel.updateMany( { refTarget: this._id }, { $pull: { refTarget: this._id } }, { session } ); });
关键注意事项
- 优先使用「查询文档实例 -> 调用文档实例
remove()方法」的方式触发删除,不要直接调用Model.deleteOne()/Model.deleteMany()等静态删除方法,这类静态方法触发的是查询中间件,无法直接获取当前操作的文档实例,级联逻辑容易出现遗漏。 - 所有级联写操作必须显式传入获取到的session,漏传session的操作不会被纳入事务,异常时这部分变更不会回滚。
- Mongoose 7+版本原生支持async中间件,不需要手动调用
next()传递控制权,中间件内直接抛错就会自动中断流程,触发事务回滚。
内容的提问来源于stack exchange,提问作者HellCatVN
相关产品推荐
相关产品推荐

