4. TypeORM 集成与事务配置:TypeORM 安装与配置、DataSource 与 EntityManager、事务管理器注入

在 NestJS 项目中,TypeORM 是连接 API 控制器与数据库的核心 ORM 层。事务边界的正确治理,首先依赖于 TypeORM 的稳定集成与事务管理器的合理注入。本章将围绕 TypeORM 的安装配置、DataSource 与 EntityManager 的角色划分,以及如何在 NestJS 的 DI 系统中注入事务管理器展开,为后续的声明式事务与编程式事务打下基础。

4.1 TypeORM 安装与模块配置

TypeORM 在 NestJS 中的集成通过 @nestjs/typeorm 包完成。安装时需同时安装 TypeORM 核心库与对应数据库驱动(以 PostgreSQL 为例)。

安装命令:
npm install @nestjs/typeorm typeorm pg

安装完成后,在根模块 AppModule 中通过 TypeOrmModule.forRoot() 进行全局配置。配置项包括数据库连接参数、实体扫描路径、以及事务相关的日志与隔离级别设置。

// app.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';

@Module({
  imports: [
    TypeOrmModule.forRoot({
      type: 'postgres',
      host: 'localhost',
      port: 5432,
      username: 'postgres',
      password: 'pass',
      database: 'nest_tx_demo',
      entities: [__dirname + '/**/*.entity{.ts,.js}'],
      synchronize: true, // 开发环境使用,生产环境应关闭
      logging: ['query', 'error'],
      extra: {
        max: 10, // 连接池最大连接数
        idleTimeoutMillis: 30000,
      },
    }),
  ],
})
export class AppModule {}
事务配置要点: 生产环境中建议将 synchronize 设为 false,使用迁移(migrations)管理表结构变更。事务隔离级别默认使用数据库的 READ COMMITTED,可在连接配置中通过 isolationLevel 调整。

4.2 DataSource 与 EntityManager 的角色划分

TypeORM 中,DataSource 和 EntityManager 是两个核心对象,它们在事务边界治理中承担不同职责。

概念 职责 事务作用域 注入方式
DataSource 管理数据库连接池、实体元数据、迁移与查询 runner 全局单例,不直接参与事务 @InjectDataSource()
EntityManager 提供实体 CRUD 操作、事务管理(transaction)、查询构建器 每个事务一个独立实例 @InjectEntityManager()

DataSource 是应用启动时创建的连接池管理者,负责初始化与销毁。它不直接执行 SQL,而是通过 createQueryRunner() 或 transaction() 方法将操作委托给 EntityManager。

EntityManager 是实际的数据操作入口。每个事务会创建一个独立的 EntityManager 实例,该实例拥有自己的数据库连接与事务上下文。在 NestJS 中,默认注入的 EntityManager 是全局的,但事务内部应使用 transaction() 回调中提供的局部 EntityManager。

常见误区: 在事务回调外部使用 @InjectEntityManager() 注入的 EntityManager 执行写操作,会导致每条 SQL 自动提交,无法形成原子事务边界。事务内的所有操作必须使用回调中传入的 EntityManager 实例。

4.3 事务管理器注入方式

NestJS 通过 @nestjs/typeorm 提供了两种注入事务管理器的方式:模块级注入 与 请求级注入。

4.3.1 模块级注入(默认方式)

在 Feature 模块中导入 TypeOrmModule.forFeature([Entity]),即可在 Service 中注入对应实体的 Repository。Repository 本身是 EntityManager 的封装,支持事务操作。

// order.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { Order } from './order.entity';
import { OrderService } from './order.service';

@Module({
  imports: [TypeOrmModule.forFeature([Order])],
  providers: [OrderService],
})
export class OrderModule {}
// order.service.ts
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Order } from './order.entity';

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

  async createOrder(data: Partial<Order>): Promise<Order> {
    return this.orderRepo.manager.transaction(async (manager) => {
      const order = manager.create(Order, data);
      return manager.save(order);
    });
  }
}

4.3.2 直接注入 EntityManager

当需要跨多个 Repository 执行事务时,可以直接注入 EntityManager,使用其 transaction() 方法统一管理事务边界。

// payment.service.ts
import { Injectable } from '@nestjs/common';
import { InjectEntityManager } from '@nestjs/typeorm';
import { EntityManager } from 'typeorm';

@Injectable()
export class PaymentService {
  constructor(
    @InjectEntityManager()
    private entityManager: EntityManager,
  ) {}

  async transferFunds(fromId: number, toId: number, amount: number): Promise<void> {
    await this.entityManager.transaction(async (manager) => {
      const fromAccount = await manager.findOne(Account, { where: { id: fromId } });
      const toAccount = await manager.findOne(Account, { where: { id: toId } });

      fromAccount.balance -= amount;
      toAccount.balance += amount;

      await manager.save(fromAccount);
      await manager.save(toAccount);
    });
  }
}
最佳实践: 在 Service 层优先使用 @InjectRepository() 注入具体 Repository,仅在跨实体事务场景下使用 @InjectEntityManager()。这样可以保持代码的职责清晰,同时避免全局 EntityManager 被误用于非事务操作。

4.4 事务配置的完整流程

下图展示了从 NestJS 模块初始化到事务执行的完整链路,重点标注了 DataSource、EntityManager 与事务管理器之间的关系。

TypeORM 事务配置与注入流程 TypeORM 事务配置与注入流程 AppModule TypeOrmModule.forRoot() DataSource 连接池 / 元数据 EntityManager(全局) @InjectEntityManager() PaymentService 跨实体事务 OrderService @InjectRepository() transaction(manager) 局部 EntityManager 实例 事务回调内必须使用 manager 参数,而非全局 EntityManager,以确保所有操作在同一个数据库连接与事务上下文中执行

4.5 事务配置清单与常见问题

以下清单总结了 TypeORM 事务配置的关键步骤与常见陷阱:

  • 安装确认: 确保 @nestjs/typeorm、typeorm 与数据库驱动版本兼容,避免因版本冲突导致事务 API 不可用。
  • 连接池配置: 在 extra 中设置 max 与 idleTimeoutMillis,防止高并发下连接耗尽导致事务超时。
  • 实体注册: 使用 TypeOrmModule.forFeature() 注册实体,确保 Repository 可被注入;未注册的实体在事务中无法通过 manager.getRepository() 获取。
  • 事务隔离级别: 默认 READ COMMITTED 满足大部分场景;对于高一致性需求(如金融转账),可在 transaction() 的选项参数中指定 isolationLevel。
  • 错误回滚: TypeORM 的 transaction() 方法在回调抛出异常时自动回滚,无需手动调用 rollback();但需确保回调内不捕获异常后静默处理。
  • 嵌套事务: TypeORM 不支持真正的嵌套事务,嵌套调用 transaction() 会复用外层事务;如需独立子事务,应使用 queryRunner 手动管理。
生产环境警告: 不要在 @InjectEntityManager() 注入的全局 EntityManager 上直接调用 save() 或 update() 进行写操作,除非你明确知道当前不在事务上下文中。建议所有写操作都通过 transaction() 回调或 Repository 方法执行,以保证事务边界的可控性。