Skip to content

NestJS 数据库集成

TypeORM 与 Prisma 在 NestJS 中的实践

目录


ORM 选型

特性TypeORMPrisma
范式Active Record / Data Mapper独立 Schema
类型安全装饰器 + 泛型自动生成类型
迁移CLI 工具完善的迁移系统
学习曲线中等
性能
生态成熟快速发展

TypeORM 集成

配置

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

@Module({
  imports: [
    TypeOrmModule.forRootAsync({
      imports: [ConfigModule],
      inject: [ConfigService],
      useFactory: (config: ConfigService) => ({
        type: 'mysql',
        host: config.get('DB_HOST'),
        port: config.get('DB_PORT'),
        username: config.get('DB_USER'),
        password: config.get('DB_PASSWORD'),
        database: config.get('DB_NAME'),
        entities: [__dirname + '/**/*.entity{.ts,.js}'],
        synchronize: false, // 生产环境禁用
        logging: config.get('NODE_ENV') === 'development'
      })
    })
  ]
})
export class AppModule {}

Entity 定义

typescript
// user.entity.ts
import { Entity, Column, PrimaryGeneratedColumn, OneToMany, CreateDateColumn } from 'typeorm';

@Entity('users')
export class User {
  @PrimaryGeneratedColumn()
  id: number;

  @Column({ unique: true })
  email: string;

  @Column()
  password: string;

  @Column({ nullable: true })
  name: string;

  @OneToMany(() => Post, post => post.author)
  posts: Post[];

  @CreateDateColumn()
  createdAt: Date;
}

Repository 模式

typescript
// users.module.ts
@Module({
  imports: [TypeOrmModule.forFeature([User])],
  providers: [UsersService],
  exports: [UsersService]
})
export class UsersModule {}

// users.service.ts
@Injectable()
export class UsersService {
  constructor(
    @InjectRepository(User)
    private readonly userRepository: Repository<User>
  ) {}

  async findAll(): Promise<User[]> {
    return this.userRepository.find({
      relations: ['posts'],
      order: { createdAt: 'DESC' }
    });
  }

  async findOne(id: number): Promise<User | null> {
    return this.userRepository.findOne({
      where: { id },
      relations: ['posts']
    });
  }

  async create(data: CreateUserDto): Promise<User> {
    const user = this.userRepository.create(data);
    return this.userRepository.save(user);
  }

  async update(id: number, data: UpdateUserDto): Promise<User> {
    await this.userRepository.update(id, data);
    return this.findOne(id);
  }

  async remove(id: number): Promise<void> {
    await this.userRepository.delete(id);
  }
}

Query Builder

typescript
async findWithFilters(filters: UserFilters): Promise<User[]> {
  const qb = this.userRepository.createQueryBuilder('user')
    .leftJoinAndSelect('user.posts', 'post');

  if (filters.email) {
    qb.andWhere('user.email LIKE :email', { email: `%${filters.email}%` });
  }

  if (filters.hasPost) {
    qb.andWhere('post.id IS NOT NULL');
  }

  return qb
    .orderBy('user.createdAt', 'DESC')
    .skip(filters.offset)
    .take(filters.limit)
    .getMany();
}

Prisma 集成

配置

typescript
// prisma.service.ts
import { Injectable, OnModuleInit, OnModuleDestroy } from '@nestjs/common';
import { PrismaClient } from '@prisma/client';

@Injectable()
export class PrismaService extends PrismaClient implements OnModuleInit, OnModuleDestroy {
  async onModuleInit() {
    await this.$connect();
  }

  async onModuleDestroy() {
    await this.$disconnect();
  }
}

// prisma.module.ts
@Global()
@Module({
  providers: [PrismaService],
  exports: [PrismaService]
})
export class PrismaModule {}

Schema 定义

prisma
// schema.prisma
generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "mysql"
  url      = env("DATABASE_URL")
}

model User {
  id        Int      @id @default(autoincrement())
  email     String   @unique
  password  String
  name      String?
  posts     Post[]
  createdAt DateTime @default(now())
}

model Post {
  id        Int      @id @default(autoincrement())
  title     String
  content   String?
  published Boolean  @default(false)
  author    User     @relation(fields: [authorId], references: [id])
  authorId  Int
}

Service 实现

typescript
// users.service.ts
@Injectable()
export class UsersService {
  constructor(private readonly prisma: PrismaService) {}

  async findAll() {
    return this.prisma.user.findMany({
      include: { posts: true },
      orderBy: { createdAt: 'desc' }
    });
  }

  async findOne(id: number) {
    return this.prisma.user.findUnique({
      where: { id },
      include: { posts: true }
    });
  }

  async create(data: CreateUserDto) {
    return this.prisma.user.create({
      data,
      include: { posts: true }
    });
  }

  async update(id: number, data: UpdateUserDto) {
    return this.prisma.user.update({
      where: { id },
      data
    });
  }

  async remove(id: number) {
    return this.prisma.user.delete({ where: { id } });
  }
}

事务处理

TypeORM 事务

typescript
// 使用 QueryRunner
async transferMoney(fromId: number, toId: number, amount: number) {
  const queryRunner = this.dataSource.createQueryRunner();
  
  await queryRunner.connect();
  await queryRunner.startTransaction();
  
  try {
    await queryRunner.manager.decrement(Account, { id: fromId }, 'balance', amount);
    await queryRunner.manager.increment(Account, { id: toId }, 'balance', amount);
    
    await queryRunner.commitTransaction();
  } catch (error) {
    await queryRunner.rollbackTransaction();
    throw error;
  } finally {
    await queryRunner.release();
  }
}

// 使用装饰器(需配置)
@Transactional()
async createUserWithProfile(data: CreateUserDto) {
  const user = await this.userRepository.save(data);
  await this.profileRepository.save({ userId: user.id });
  return user;
}

Prisma 事务

typescript
// 交互式事务
async transferMoney(fromId: number, toId: number, amount: number) {
  return this.prisma.$transaction(async (tx) => {
    const from = await tx.account.update({
      where: { id: fromId },
      data: { balance: { decrement: amount } }
    });

    if (from.balance < 0) {
      throw new BadRequestException('Insufficient balance');
    }

    await tx.account.update({
      where: { id: toId },
      data: { balance: { increment: amount } }
    });

    return { success: true };
  });
}

// 批量事务
async createBatch(users: CreateUserDto[]) {
  return this.prisma.$transaction(
    users.map(user => this.prisma.user.create({ data: user }))
  );
}

Repository 模式封装

通用 Repository

typescript
// base.repository.ts
export abstract class BaseRepository<T> {
  constructor(protected readonly prisma: PrismaService) {}

  abstract get model(): any;

  async findAll(args?: any): Promise<T[]> {
    return this.model.findMany(args);
  }

  async findOne(id: number): Promise<T | null> {
    return this.model.findUnique({ where: { id } });
  }

  async create(data: any): Promise<T> {
    return this.model.create({ data });
  }

  async update(id: number, data: any): Promise<T> {
    return this.model.update({ where: { id }, data });
  }

  async delete(id: number): Promise<T> {
    return this.model.delete({ where: { id } });
  }
}

// users.repository.ts
@Injectable()
export class UsersRepository extends BaseRepository<User> {
  constructor(prisma: PrismaService) {
    super(prisma);
  }

  get model() {
    return this.prisma.user;
  }

  async findByEmail(email: string): Promise<User | null> {
    return this.model.findUnique({ where: { email } });
  }
}

高频面试题

Q1: TypeORM 和 Prisma 如何选择?

场景推荐
类型安全优先Prisma
复杂查询多TypeORM (Query Builder)
快速开发Prisma
已有 TypeORM 经验TypeORM

Q2: 如何处理 N+1 问题?

typescript
// TypeORM - 使用 relations
const users = await this.userRepository.find({
  relations: ['posts', 'posts.comments']
});

// Prisma - 使用 include
const users = await this.prisma.user.findMany({
  include: { posts: { include: { comments: true } } }
});

Q3: NestJS 中事务如何与 Service 结合?

  1. 注入 DataSource/PrismaService:在 Service 中管理事务
  2. 自定义装饰器:封装事务逻辑
  3. Interceptor:请求级事务(不推荐,粒度过粗)

Q4: 如何实现软删除?

typescript
// TypeORM
@Entity()
export class User {
  @DeleteDateColumn()
  deletedAt: Date;
}

// 查询自动排除已删除
const users = await this.userRepository.find();

// 查询包含已删除
const users = await this.userRepository.find({ withDeleted: true });

// Prisma - 中间件实现
this.prisma.$use(async (params, next) => {
  if (params.action === 'delete') {
    params.action = 'update';
    params.args.data = { deletedAt: new Date() };
  }
  return next(params);
});

迁移管理

TypeORM

bash
# 生成迁移
npx typeorm migration:generate -n AddUserTable

# 运行迁移
npx typeorm migration:run

# 回滚
npx typeorm migration:revert

Prisma

bash
# 创建迁移
npx prisma migrate dev --name add_user_table

# 生产环境部署
npx prisma migrate deploy

# 重置数据库
npx prisma migrate reset

前端面试知识库