4. NestJS TypeORM 事务管理:@Transaction 装饰器、QueryRunner 手动事务、事务传播行为

在微服务架构中,单个 API 请求往往涉及多个数据库写操作。事务管理是保证数据一致性的第一道防线。NestJS 结合 TypeORM 提供了三种事务实现方式:装饰器声明式、QueryRunner 手动式以及事务传播行为控制。本章将逐一剖析它们的适用场景、实现细节与常见陷阱。

4.1 事务基础概念回顾

事务(Transaction)是一组不可分割的数据库操作,满足 ACID 特性。在 NestJS + TypeORM 中,事务边界决定了哪些操作属于同一个原子单元。

ACID 特性 说明 事务边界影响
原子性 事务内所有操作要么全部成功,要么全部回滚 边界过窄可能导致部分操作未纳入回滚范围
一致性 事务前后数据满足所有约束 边界需覆盖所有相关表的写入
隔离性 并发事务互不干扰 传播行为决定嵌套事务的隔离级别
持久性 提交后数据永久保存 回滚边界必须明确

4.2 @Transaction 装饰器:声明式事务管理

@Transaction() 装饰器是 TypeORM 提供的最简洁的事务声明方式,适用于单个服务方法内需要事务保护的场景。它自动创建并管理 EntityManager 实例,开发者只需在方法参数中注入 @TransactionManager() 即可。

核心用法:在 Service 方法上添加 @Transaction(),方法参数中通过 @TransactionManager() 获取事务性的 EntityManager。
import { Transaction, TransactionManager, EntityManager } from 'typeorm';
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Order } from './order.entity';
import { Inventory } from './inventory.entity';

@Injectable()
export class OrderService {
  constructor(
    @InjectRepository(Order)
    private orderRepo: Repository<Order>,
    @InjectRepository(Inventory)
    private inventoryRepo: Repository<Inventory>,
  ) {}

  @Transaction()
  async createOrderWithTransaction(
    orderData: Partial<Order>,
    productId: number,
    quantity: number,
    @TransactionManager() manager: EntityManager,
  ): Promise<Order> {
    // 1. 创建订单
    const order = this.orderRepo.create(orderData);
    const savedOrder = await manager.save(order);

    // 2. 扣减库存(使用同一个 manager 保证事务性)
    const inventory = await manager.findOne(Inventory, { where: { productId } });
    if (!inventory || inventory.stock < quantity) {
      throw new Error('库存不足');
    }
    inventory.stock -= quantity;
    await manager.save(inventory);

    return savedOrder;
  }
}
⚠️ 注意:@Transaction() 装饰器在 TypeORM 0.3.x 版本中已被标记为废弃,官方推荐使用 QueryRunner 或 DataSource.transaction() 方法。如果你的项目仍在使用 0.2.x 版本,可以继续使用;新项目建议直接采用手动事务方式。

4.3 QueryRunner 手动事务:精细控制事务边界

当需要跨多个 Service 方法、Repository 或数据源执行事务时,QueryRunner 提供了最灵活的手动控制能力。开发者可以显式调用 startTransaction()、commitTransaction() 和 rollbackTransaction()。

最佳实践:在微服务中,建议将 QueryRunner 的生命周期绑定到请求级别,通过自定义拦截器或中间件统一管理,避免事务泄漏。
import { Injectable } from '@nestjs/common';
import { InjectDataSource } from '@nestjs/typeorm';
import { DataSource } from 'typeorm';
import { Order } from './order.entity';
import { Payment } from './payment.entity';

@Injectable()
export class PaymentService {
  constructor(
    @InjectDataSource()
    private dataSource: DataSource,
  ) {}

  async processPaymentWithManualTransaction(
    orderId: number,
    amount: number,
  ): Promise<void> {
    const queryRunner = this.dataSource.createQueryRunner();
    await queryRunner.connect();
    await queryRunner.startTransaction();

    try {
      // 1. 更新订单状态
      await queryRunner.manager.update(
        Order,
        { id: orderId },
        { status: 'paid' },
      );

      // 2. 创建支付记录
      const payment = queryRunner.manager.create(Payment, {
        orderId,
        amount,
        status: 'success',
      });
      await queryRunner.manager.save(payment);

      // 3. 提交事务
      await queryRunner.commitTransaction();
    } catch (error) {
      // 4. 发生异常时回滚
      await queryRunner.rollbackTransaction();
      throw error;
    } finally {
      // 5. 释放 QueryRunner 资源
      await queryRunner.release();
    }
  }
}

4.4 事务传播行为:嵌套事务与隔离策略

事务传播行为定义了当多个事务性方法相互调用时,事务边界的合并策略。TypeORM 支持三种传播行为:REQUIRED、REQUIRES_NEW 和 NESTED。

传播行为 行为描述 适用场景
REQUIRED 加入当前事务,如果不存在则创建新事务 默认行为,适用于大多数业务场景
REQUIRES_NEW 始终创建新事务,挂起当前事务 日志记录、审计等需要独立提交的操作
NESTED 在当前事务内创建保存点,支持部分回滚 批量处理中允许跳过失败项
传播行为配置示例:在 TypeORM 中,传播行为通过 @Transaction({ propagation: 'REQUIRES_NEW' }) 参数指定。
@Transaction({ propagation: 'REQUIRES_NEW' })
async logAuditTrail(
  action: string,
  entityId: number,
  @TransactionManager() manager: EntityManager,
): Promise<void> {
  const auditLog = manager.create(AuditLog, { action, entityId });
  await manager.save(auditLog);
  // 即使外层事务回滚,审计日志也会独立提交
}

4.5 三种事务方式的对比与选型

维度 @Transaction 装饰器 QueryRunner 手动事务 DataSource.transaction()
控制粒度 方法级别 代码块级别 回调函数级别
跨 Service 支持 困难(需传递 manager) 容易(可传递 queryRunner) 中等(通过回调闭包)
异常回滚 自动回滚 手动 try/catch 自动回滚
传播行为 支持 需手动实现 不支持
推荐场景 简单单方法事务(旧版) 复杂跨服务事务 新版推荐,简洁安全

4.6 事务边界常见错误与补偿策略

在实际微服务开发中,事务边界错误往往表现为以下三种模式:

  • 事务泄漏:QueryRunner 未在 finally 块中 release,导致数据库连接池耗尽。
  • 跨服务事务:一个事务跨越多个微服务,无法保证分布式 ACID。
  • 嵌套事务回滚异常:REQUIRES_NEW 内部异常未正确处理,导致外层事务也被标记为回滚。
⚠️ 关键提醒:在微服务中,不要试图用数据库本地事务解决跨服务的一致性问题。本地事务只适用于单个服务内的写操作。跨服务场景应使用 Saga 模式或事件驱动补偿机制(后续章节详解)。

4.7 本章小结

事务管理是微服务 API 数据一致性的基石。通过本章学习,你应该掌握:

  • 使用 @Transaction() 装饰器快速声明事务边界(适用于旧版 TypeORM)。
  • 使用 QueryRunner 手动控制事务的开启、提交、回滚与资源释放。
  • 理解 REQUIRED、REQUIRES_NEW、NESTED 三种传播行为的差异与选型。
  • 识别事务边界常见错误,并建立正确的补偿意识。