珠峰 2024年Nest.js体系课 | 完结

91 阅读4分钟

Nest.js 数据库专题:TypeORM 与 Prisma 集成、事务与优化最佳实践

一、TypeORM 与 Prisma 集成对比

  1. TypeORM:NestJS 官方推荐的核心选择

    • 技术成熟度:作为 Node.js 生态最成熟的 TypeScript ORM,TypeORM 经过长期迭代,支持 MySQL、PostgreSQL、SQLite 等主流数据库,提供事务管理、复杂查询、存储过程等企业级功能。
    • 设计模式灵活性
      • Active Record:实体类直接封装数据访问逻辑(如 User.findByName()),适合简单应用,代码简洁但耦合度高。
      • Data Mapper:通过独立 Repository 类处理数据库操作(如 userRepository.find()),实现业务逻辑与数据访问解耦,适合大型复杂项目。
    • 生态与迁移工具
      • 社区规模庞大,第三方资源丰富,官方文档详细覆盖从基础到高级的所有功能。
      • 提供迁移工具和 synchronize: true 选项(开发环境方便,生产环境需禁用),推荐使用迁移脚本管理数据库变更,确保可控性。
    • NestJS 集成:通过 @nestjs/typeorm 包无缝集成,依赖注入、中间件等特性可构建可扩展应用。
  2. Prisma:类型安全与开发体验的革新者

    • 类型安全与开发体验:Prisma Client 自动生成类型安全的 API,减少运行时错误;链式 API 简化查询(如 prisma.user.findMany()),提升开发效率。
    • Schema 优先设计:通过 schema.prisma 文件定义数据模型、字段及关系,支持声明式数据库变更管理(Prisma Migrate),生成最小差异集并支持回滚。
    • 局限性
      • 功能完善度:作为新兴 ORM,复杂迁移(如存储过程修改)、多数据库兼容性仍在完善中,处理复杂数据库变更时可能需手动干预。
      • 生态规模:社区规模小于 TypeORM,旧版数据库兼容性等边缘场景资源较少。
    • NestJS 集成:需通过第三方库(如 nestjs-prisma)集成,官方支持程度不如 TypeORM;Schema 定义与 NestJS 实体模型需额外映射,增加开发复杂度。
  3. 集成方案对比

    • TypeORM 集成步骤

      1. 安装依赖:npm install @nestjs/typeorm typeorm pg(PostgreSQL 示例)。
      2. 配置数据库连接:
        // app.module.ts
        TypeOrmModule.forRoot({
          type: 'postgres',
          host: 'localhost',
          port: 5432,
          username: 'postgres',
          password: 'password',
          database: 'test',
          entities: [__dirname + '/../**/*.entity{.ts,.js}'],
          synchronize: true, // 开发环境使用
        }),
        
      3. 定义实体:
        // user.entity.ts
        @Entity()
        export class User {
          @PrimaryGeneratedColumn()
          id: number;
        
          @Column()
          name: string;
        }
        
      4. 使用 Repository 操作数据:
        // user.service.ts
        @Injectable()
        export class UserService {
          constructor(
            @InjectRepository(User)
            private userRepository: Repository<User>,
          ) {}
        
          findAll(): Promise<User[]> {
            return this.userRepository.find();
          }
        }
        
    • Prisma 集成步骤

      1. 安装依赖:npm install prisma @prisma/client
      2. 初始化 Prisma:npx prisma init,生成 schema.prisma.env
      3. 配置数据库连接:
        // schema.prisma
        datasource db {
          provider = "postgresql"
          url      = env("DATABASE_URL")
        }
        
      4. 定义数据模型:
        model User {
          id    Int     @id @default(autoincrement())
          name  String
        }
        
      5. 生成 Prisma Client:npx prisma generate
      6. 创建 Prisma 服务:
        // prisma.service.ts
        @Injectable()
        export class PrismaService extends PrismaClient implements OnModuleInit {
          async onModuleInit() {
            await this.$connect();
          }
        }
        
      7. 注册 Prisma 模块:
        // prisma.module.ts
        @Module({
          providers: [PrismaService],
          exports: [PrismaService],
        })
        export class PrismaModule {}
        
      8. 在控制器中使用:
        // app.controller.ts
        @Controller()
        export class AppController {
          constructor(private prismaService: PrismaService) {}
        
          @Get()
          async findAll() {
            return this.prismaService.user.findMany();
          }
        }
        

二、事务管理最佳实践

  1. TypeORM 事务管理

    • 方式一:QueryRunner(手动控制事务生命周期):
      async transferMoney(fromId: number, toId: number, amount: number) {
        const queryRunner = this.dataSource.createQueryRunner();
        await queryRunner.connect();
        await queryRunner.startTransaction();
        try {
          await queryRunner.manager
            .createQueryBuilder(User, 'user')
            .update(User)
            .set({ balance: () => `balance - ${amount}` })
            .where('id = :id', { id: fromId })
            .execute();
      
          await queryRunner.manager
            .createQueryBuilder(User, 'user')
            .update(User)
            .set({ balance: () => `balance + ${amount}` })
            .where('id = :id', { id: toId })
            .execute();
      
          await queryRunner.commitTransaction();
        } catch (err) {
          await queryRunner.rollbackTransaction();
          throw err;
        } finally {
          await queryRunner.release();
        }
      }
      
    • 方式二:装饰器自动事务(通过 @Transaction()@TransactionManager()):
      @Injectable()
      export class UserService {
        constructor(
          @InjectRepository(User)
          private userRepository: Repository<User>,
          @InjectEntityManager()
          private entityManager: EntityManager,
        ) {}
      
        @Transaction()
        async updateUserWithTransaction(
          @TransactionManager() entityManager: EntityManager,
          id: number,
          data: Partial<User>,
        ) {
          await entityManager.update(User, id, data);
          await entityManager.query('LOG UPDATE OPERATION');
        }
      }
      
  2. Prisma 事务管理

    • 方式一:$transaction 函数(自动回滚):
      async transferMoney(fromId: number, toId: number, amount: number) {
        await this.prismaService.$transaction(async (tx) => {
          const fromUser = await tx.user.findUnique({ where: { id: fromId } });
          if (fromUser.balance < amount) {
            throw new Error('Insufficient balance');
          }
      
          await tx.user.update({
            where: { id: fromId },
            data: { balance: { decrement: amount } },
          });
      
          await tx.user.update({
            where: { id: toId },
            data: { balance: { increment: amount } },
          });
        });
      }
      
    • 方式二:事务隔离级别控制(需手动实现,Prisma 默认支持可序列化隔离级别)。

三、性能优化策略

  1. TypeORM 优化

    • 查询优化
      • 使用 select: false 隐藏敏感字段(如密码):
        @Column({ select: false })
        password: string;
        
      • 避免 N+1 查询问题,使用 eagerlazy 关系加载:
        @OneToMany(() => Post, (post) => post.author, { eager: true })
        posts: Post[];
        
    • 连接池配置:调整 maxQueryExecutionTimeconnectionTimeout 避免长时间挂起。
    • 索引优化:在高频查询字段上添加索引:
      @Index()
      @Column({ unique: true })
      email: string;
      
  2. Prisma 优化

    • 查询优化
      • 使用 select 减少不必要的数据加载:
        const user = await prisma.user.findUnique({
          where: { id: 1 },
          select: { id: true, name: true },
        });
        
      • 批量操作替代循环单条操作:
        await prisma.user.createMany({
          data: [
            { name: 'Alice' },
            { name: 'Bob' },
          ],
        });
        
    • 中间件性能监控:通过 Prisma 的 logging 选项分析慢查询:
      const prisma = new PrismaClient({
        log: ['query', 'info', 'warn'],
      });
      
  3. 通用优化技巧

    • 缓存层集成:使用 Redis 缓存高频查询结果(如用户信息)。
    • 读写分离:主库写,从库读,通过 TypeORM/Prisma 的多数据源配置实现。
    • 分页查询:避免返回全量数据,使用 takeskip
      const users = await prisma.user.findMany({
        take: 10,
        skip: 20,
      });
      

四、选型建议

  1. 选择 TypeORM 的场景

    • 项目需要支持多种数据库(如 MySQL、PostgreSQL、SQLite)。
    • 需要复杂事务管理或存储过程调用。
    • 依赖 NestJS 官方生态,追求长期稳定性。
  2. 选择 Prisma 的场景

    • 项目以 TypeScript 为主,强调类型安全和开发效率。
    • 需要快速迭代,利用 Prisma Studio 可视化工具加速开发。
    • 数据库模式相对简单,无需复杂迁移。