NestJS+TypeORM操作MongoDB事务未回滚问题求助
问题描述
使用TypeORM搭配MongoDB进行事务测试时,当第二个保存操作因重复数据报错,第一个已执行的保存操作无法回滚。相关代码如下:
import { Injectable, Logger } from '@nestjs/common'; import { InjectDataSource, InjectRepository } from '@nestjs/typeorm'; import { Avatar } from '../entity/avatar.entity'; import { Repository, EntityManager, QueryRunner, DataSource } from 'typeorm' import { AvatarRepository } from '../../domain/repository/avatarRepository.interface'; import { AvatarM } from '../../domain/model/avatar'; @Injectable() export class DatabaseAvatarRepository implements AvatarRepository { constructor( @InjectRepository(Avatar) private readonly avatarEntity: Repository<AvatarM>, private readonly dataSource: DataSource, private readonly manager: EntityManager ) { } async transTest() { const a = this.avatarEntity.create({ userId: 1, hashedName: 'aaa' }) // 本应成功保存 const b = this.avatarEntity.create({ userId: 2, hashedName: 'bbb' }) // 因重复数据报错 await this.dataSource.transaction(async (transactionalEntityManager) => { await transactionalEntityManager.save(a) await transactionalEntityManager.save(b) }) } }
问题原因与修复方案
核心原因
- MongoDB事务支持限制:MongoDB仅在副本集(Replica Set)或分片集群模式下支持事务,单实例MongoDB无法触发事务回滚,所有操作会直接提交。
- 实体上下文不匹配:通过默认
Repository创建的实体绑定到全局EntityManager,而非事务内的transactionalEntityManager,导致事务管理器无法追踪这些实体的操作状态,save操作可能绕过事务直接执行。
修复步骤
1. 切换MongoDB为副本集模式
即使是本地测试,也需要配置单节点副本集:
- 停止当前单实例MongoDB服务
- 启动时指定
--replSet参数,例如:mongod --replSet rs0 --dbpath ./data - 进入MongoDB shell,执行
rs.initiate()初始化副本集
2. 修改代码适配事务上下文
将实体创建或绑定到事务内的transactionalEntityManager,确保操作在事务上下文执行:
方案一:直接在事务内创建实体
async transTest() { await this.dataSource.transaction(async (transactionalEntityManager) => { // 使用事务内的管理器创建实体 const a = transactionalEntityManager.create(Avatar, { userId: 1, hashedName: 'aaa' }); const b = transactionalEntityManager.create(Avatar, { userId: 2, hashedName: 'bbb' }); await transactionalEntityManager.save(a); await transactionalEntityManager.save(b); }); }
方案二:合并外部实体到事务上下文
如果需要保留外部创建的实体,使用merge方法将实体关联到事务管理器:
async transTest() { const a = this.avatarEntity.create({ userId: 1, hashedName: 'aaa' }); const b = this.avatarEntity.create({ userId: 2, hashedName: 'bbb' }); await this.dataSource.transaction(async (transactionalEntityManager) => { // 将实体合并到事务上下文 const txA = transactionalEntityManager.merge(Avatar, a); const txB = transactionalEntityManager.merge(Avatar, b); await transactionalEntityManager.save(txA); await transactionalEntityManager.save(txB); }); }
内容的提问来源于stack exchange,提问作者morteza mortezaie
相关产品推荐
相关产品推荐

