Express+Mongoose多表关联操作如何实现全部验证通过才执行保存
解决方案:使用MongoDB事务实现多集合写入原子性
你遇到的问题是多集合分步写入没有原子性保证,部分写入成功后后续操作失败会残留脏数据,MongoDB从4.0版本开始支持多文档事务,可以保证同一个事务内的所有写入操作要么全部成功,要么全部回滚。
前置要求
- MongoDB版本≥4.0,部署为副本集/分片集群,本地单节点测试可通过简单配置开启事务支持
- 使用Mongoose作为ODM操作数据库
具体实现步骤
1. 核心逻辑调整
将所有集合的写入操作放在同一个事务会话中执行,出现任何异常直接终止事务,所有已执行的写入会自动回滚。同时我们可以先主动触发所有Schema的校验逻辑,提前拦截参数错误,减少不必要的事务开销。
2. 修改后代码示例
const mongoose = require('mongoose') // 你的其他模型引入省略 // 注册接口逻辑 async function register(req, res) { let session try { // 1. 开启会话并启动事务 session = await mongoose.startSession() session.startTransaction() // 2. 创建所有实例,主动触发校验,提前抛错避免进入写入流程 const email = new UserEmail({ email: req.body.email }) await email.validate() // 主动触发邮箱格式校验 const auth = new UserPassword({ password: req.body.password, }) await auth.validate() // 主动触发密码长度等校验 const user = new User({ email: email._id, auth: auth._id, }) await user.validate() // 主动触发User模型的校验 // 3. 所有校验通过,执行事务内写入 const result = await user.save({ session }) // 补全关联字段(你原代码漏了UserEmail和UserPassword的user关联赋值) email.user = user._id await email.save({ session }) auth.user = user._id await auth.save({ session }) // 4. 所有操作成功,提交事务 await session.commitTransaction() // 返回数据 const data = result.toJSON() res.send(data) } catch (err) { // 出现任何异常,终止事务,所有写入自动回滚 if (session) { await session.abortTransaction() } // 你的其他错误处理逻辑,比如返回错误提示 res.status(400).send({ message: err.message || '注册失败' }) } finally { // 关闭会话 if (session) { session.endSession() } } }
兜底兼容方案(无事务环境可用)
如果你本地暂时无法配置支持事务的MongoDB环境,可以用手动补偿的方式实现类似效果,可靠性略低于事务,但可满足作业需求:
try { const email = new UserEmail({ email: req.body.email }) await email.validate() const auth = new UserPassword({ password: req.body.password }) await auth.validate() const user = new User({ email: email._id, auth: auth._id }) await user.validate() // 按顺序保存,每一步保存成功后记录标识 let savedEmail = false, savedAuth = false, savedUser = false await email.save() savedEmail = true await auth.save() savedAuth = true const result = await user.save() savedUser = true // 补全关联 email.user = user._id await email.save() auth.user = user._id await auth.save() res.send(result.toJSON()) } catch (err) { // 出错时按保存顺序反向删除已写入的数据 if (savedEmail) await UserEmail.findByIdAndDelete(email._id) if (savedAuth) await UserPassword.findByIdAndDelete(auth._id) if (savedUser) await User.findByIdAndDelete(user._id) res.status(400).send({ message: err.message }) }
内容的提问来源于stack exchange,提问作者FreeLancer
相关产品推荐
相关产品推荐

