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三种传播行为的差异与选型。 - 识别事务边界常见错误,并建立正确的补偿意识。