You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

NestJS结合TypeORM如何实现跨服务事务及异常回滚

单应用多服务拆分场景下的跨服务事务实现方案

问题背景

  • 目标:实现项目内跨服务事务能力,异常抛出时事务可正常回滚
  • 问题复现:最初编写的代码抛出异常时事务无法回滚,对应代码如下:
    事务不生效的初始代码
  • 验证基准:未拆分逻辑的单块代码可正常触发回滚,对应可正常运行的代码如下:
    可正常回滚的单块代码

当前已编写的事务代码如下:

public async createProduct(dto: CreateProductDto): Promise<ProductEntity> {
  const product = await this.dataSource.transaction(async (entityManager) => {
    dto.price = await this.priceService.createPrice(
      entityManager,
      dto.unitPrice,
    );
    throw new BadRequestException('bad');
    return await entityManager
      .getRepository(ProductEntity)
      .create({ ...dto })
      .save();
  });
  console.log(product);

  return product;
}

核心诉求:将不同实体的创建逻辑拆分到对应独立服务后,依然保证事务生效,异常时全链路回滚。


根因说明

事务回滚失效的核心原因是:拆分到独立子服务的数据库操作,没有复用当前事务绑定的EntityManager实例,而是使用了服务初始化时注入的全局数据源/仓库连接,导致子服务的数据库操作和外层开启的事务不在同一个数据库会话中,外层抛出异常时自然无法回滚子服务已经提交的操作。

具体实现方案

当前基于TypeORM手动传递EntityManager的思路是可行的,只要统一约束所有事务链路内的子服务操作规则即可,单库场景下不需要引入额外的分布式事务组件:

  • 事务的开启、提交/回滚逻辑统一收敛在最外层的业务聚合层,子服务内部不要单独开启事务
  • 所有涉及事务写操作的子服务方法,新增可选的EntityManager入参,方法内部优先使用传入的EntityManager获取仓库执行操作,非事务场景下不传参则使用服务默认注入的数据源
  • 事务回调块内只要抛出未捕获的异常,TypeORM会自动触发全事务回滚,不需要手动编写回滚逻辑

代码改造示例

  1. 子服务(PriceService)改造,支持事务管理器透传
@Injectable()
export class PriceService {
  constructor(@InjectDataSource() private readonly dataSource: DataSource) {}

  /**
   * 创建价格记录
   * @param unitPrice 单价
   * @param manager 事务场景下传入绑定事务的EntityManager,非事务场景可不传
   */
  async createPrice(unitPrice: number, manager?: EntityManager): Promise<PriceEntity> {
    const priceRepo = manager 
      ? manager.getRepository(PriceEntity) 
      : this.dataSource.getRepository(PriceEntity);
    
    return priceRepo.create({ unitPrice }).save();
  }
}
  1. 外层聚合服务(ProductService)事务调用,和现有写法完全对齐,只要保证所有子服务操作传入同一个EntityManager即可
public async createProduct(dto: CreateProductDto): Promise<ProductEntity> {
  const product = await this.dataSource.transaction(async (entityManager) => {
    // 所有子服务操作都传入当前事务绑定的entityManager,保证操作在同一会话内
    dto.price = await this.priceService.createPrice(dto.unitPrice, entityManager);

    // 此处抛出异常时,上方createPrice的写入操作会被一并回滚
    throw new BadRequestException('bad');

    const productRepo = entityManager.getRepository(ProductEntity);
    return productRepo.create({ ...dto }).save();
  });

  return product;
}

补充说明

  • 如果服务是跨独立数据库、跨进程部署的微服务架构,上述同库共享EntityManager的方案不适用,需要根据业务场景选择TCC、可靠消息最终一致性、Seata等分布式事务方案
  • 不要在事务回调块内执行HTTP调用、RPC请求等长耗时IO操作,避免长时间占用数据库连接,降低服务并发能力
  • 如果觉得逐层传递EntityManager参数太繁琐,可以基于CLS(连续本地存储)实现事务上下文的自动透传,不需要手动传参,但手动传参的方式调试成本最低、逻辑最直观,适合中小项目快速落地

内容的提问来源于stack exchange,提问作者C TC

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.27 06:24:25