TypeORM事务中未提交企业行重复创建问题解决方案咨询
我有个companies表,taxId列加了唯一约束(UQ)。代码里写了companiesRepository类,包含三个方法:
findCompany:通过taxId查询企业createCompany:执行INSERT操作创建企业findOrCreate:传入taxId,先调用findCompany查询,未找到则调用createCompany创建
现在用TypeORM的queryRunner管理全局事务,处理接口接收的企业+发票树形结构数据,确保流程出错时能回滚。但同一请求的payload里可能包含多张同一家未注册企业的发票:第一次调用findOrCreate会创建该企业,但由于事务未提交,后续调用findOrCreate查不到这条未提交的记录,会尝试重复创建,触发唯一约束违例异常。
我目前有两个思路:取消企业创建的事务(可行但不合理);用单独的queryManager创建企业并立即提交(能让后续查询到)。但想知道:能不能在当前接口的全局queryManager事务内,查询到未提交的行?
当然可以,问题出在操作的连接绑定或者事务隔离级别上,下面是具体解决办法:
1. 所有操作绑定同一个事务实例
TypeORM中,事务内的所有读写必须使用同一个QueryRunner实例(或者它生成的EntityManager),否则后续查询会走独立的数据库连接,看不到当前事务未提交的变更。
比如你的companiesRepository方法如果用的是全局EntityManager而非事务绑定的实例,就会出现查不到未提交数据的情况。正确做法:
- 事务启动后,用
queryRunner.manager获取绑定当前事务的EntityManager - 所有
findCompany、createCompany调用都使用这个事务专属的实例执行
示例代码:
// 开启事务 const queryRunner = connection.createQueryRunner(); await queryRunner.startTransaction(); try { // 获取事务绑定的EntityManager const txEntityManager = queryRunner.manager; // 处理每张发票时,传入这个EntityManager到findOrCreate for (const invoice of invoices) { await companiesRepository.findOrCreate(invoice.taxId, txEntityManager); } await queryRunner.commitTransaction(); } catch (err) { await queryRunner.rollbackTransaction(); } finally { await queryRunner.release(); } // 修改findOrCreate方法,接收事务EntityManager async findOrCreate(taxId: string, entityManager: EntityManager) { // 用事务实例查询 const company = await entityManager.findOne(Company, { where: { taxId } }); if (company) return company; // 用事务实例创建并保存 const newCompany = entityManager.create(Company, { taxId }); return entityManager.save(newCompany); }
2. 调整事务隔离级别
如果数据库默认隔离级别是REPEATABLE READ(比如MySQL默认),事务内后续查询可能会读取事务启动时的快照,看不到新创建的行。可以手动设置为READ COMMITTED,此时同一事务内就能看到自己未提交的变更:
await queryRunner.startTransaction("READ COMMITTED");
3. 本地缓存优化(可选)
还可以在请求上下文里维护一个内存缓存,保存taxId -> Company的映射:每次调用findOrCreate先查缓存,命中直接返回;未命中再走数据库查询+创建,创建后同步到缓存。这样既能避免重复创建,又不破坏事务的原子性。
核心问题是事务内操作未绑定同一个连接/EntityManager,只要把所有读写都挂到当前事务的QueryRunner上,就能在事务内查询到未提交的行,完全不用拆分事务或者单独提交企业创建操作。
内容的提问来源于stack exchange,提问作者sampaiorafael

