如何在NestJS-TypeORM仓储模式中实现多查询单事务?
在TypeORM + NestJS中实现跨仓储的事务操作
问题描述
我搭建了一个使用TypeORM和PostgreSQL数据库的NestJS应用,当前采用仓储模式进行数据库查询。现在希望为查询添加数据库事务,需要在同一个事务中执行来自不同仓储的多个查询,但事务需要使用EntityManager才能让所有查询共享同一事务上下文。
现有两个服务类的实现:
EntityOneService
@Injectable() export class EntityOneService extends BaseService<EntityOne> { repository: Repository<EntityOne>; constructor(private connection: Connection) { super(); this.repository = this.connection.getRepository(EntityOne); } }
EntityTwoService
import { Injectable } from '@nestjs/common'; import { Connection, Repository } from 'typeorm'; import { EntityTwo } from '../entities/lesson.entity'; import { BaseService } from './baseService/base-service.service'; @Injectable() export class EntityTwoService extends BaseService<EntityTwo> { repository: Repository<EntityTwo>; constructor(private connection: Connection) { super(); this.repository = this.connection.getRepository(EntityTwo); } }
希望让以下两个查询处于同一个事务中:
const entityOne = await this.entityOneService.find() const entityTwo = await this.entityTwoService.find()
之前使用Sequelize时可以通过如下方式实现:
this.sequelize.transaction(async transaction => { const entityOne = await this.entityOneService.findAll({transaction}) const entityTwo = await this.entityTwoService.findAll({transaction}) })
想了解TypeORM中如何实现类似功能。
解决方案
TypeORM可以通过Connection.transaction()方法创建事务上下文,只需让服务类支持接收EntityManager参数,即可实现跨仓储的事务共享。
步骤1:修改服务类,支持传入EntityManager
在服务类的业务方法中添加可选参数,允许传入事务上下文的EntityManager,如果不传则使用默认仓库:
EntityOneService 修改后
@Injectable() export class EntityOneService extends BaseService<EntityOne> { repository: Repository<EntityOne>; constructor(private connection: Connection) { super(); this.repository = this.connection.getRepository(EntityOne); } async find(manager?: EntityManager) { // 优先使用传入的事务管理器,否则用默认仓库 const targetRepo = manager ? manager.getRepository(EntityOne) : this.repository; return targetRepo.find(); } }
EntityTwoService 修改后
import { Injectable } from '@nestjs/common'; import { Connection, Repository, EntityManager } from 'typeorm'; import { EntityTwo } from '../entities/lesson.entity'; import { BaseService } from './baseService/base-service.service'; @Injectable() export class EntityTwoService extends BaseService<EntityTwo> { repository: Repository<EntityTwo>; constructor(private connection: Connection) { super(); this.repository = this.connection.getRepository(EntityTwo); } async find(manager?: EntityManager) { const targetRepo = manager ? manager.getRepository(EntityTwo) : this.repository; return targetRepo.find(); } }
步骤2:在业务逻辑中用事务包裹查询
通过connection.transaction()创建事务,在回调函数中拿到事务对应的EntityManager,并传递给两个服务的方法:
// 在需要执行事务的业务服务/控制器中 async executeQueriesInTransaction() { const transactionResult = await this.connection.transaction(async (transactionManager) => { // 传入事务上下文的manager,确保两个查询共享同一事务 const entityOneList = await this.entityOneService.find(transactionManager); const entityTwoList = await this.entityTwoService.find(transactionManager); // 若有数据修改操作,同样使用该transactionManager执行 // await transactionManager.save(EntityOne, { name: 'new item' }); return { entityOneList, entityTwoList }; }); return transactionResult; }
关键说明
connection.transaction()会自动处理事务生命周期:回调函数内抛出异常时自动回滚事务,正常执行完毕则自动提交。- 所有需要在同一事务中执行的操作,必须使用事务回调中的
EntityManager获取仓库或执行CRUD,才能保证共享事务上下文。 - 若项目中有通用的
BaseService,可以在Base层统一添加支持EntityManager的逻辑,避免每个服务重复编写。
内容的提问来源于stack exchange,提问作者KESHAV KABRA
相关产品推荐
相关产品推荐

