一、 Prisma 核心概念
1.1 Prisma 是什么
Prisma 是下一代 Node.js 和 TypeScript ORM。与传统的 ORM(如 TypeORM)不同,Prisma 不使用类(Classes)来映射数据库表,而是使用自定义的 Schema 语言来定义模型,并生成 类型安全(Type-safe) 的查询构建器(Client)。
1.2 核心组件
Prisma 生态系统由三个主要工具组成:
- Prisma Client: 基于你的 Schema 自动生成的类型安全查询构建器,供 Node.js 和 TypeScript 调用。
- Prisma Migrate: 声明式的数据建模与迁移系统。
- Prisma Studio: 现代化的数据库 GUI,用于浏览和管理数据(类似 DBeaver/Navicat 的 Web 精简版)。
二、 NestJS 集成与初始化
2.1 项目初始化
在现有的 NestJS 项目中安装 Prisma CLI 和 Client:
bash
# 安装 CLI (开发依赖)
pnpm add -D prisma
# 安装 Client (运行时依赖)
pnpm add @prisma/client
# 初始化 (生成 prisma 文件夹和 .env, prisma.config.ts)
npx prisma init
# 安装 dotenv 用于加载环境变量
pnpm add -D dotenv
2.2 核心文件详解
Prisma 7 引入了 配置即代码 的理念,核心配置分散在以下文件中:
prisma.config.ts:Prisma 的主配置文件,负责指定 Schema 文件位置、配置迁移文件生成路径以及配置数据源连接。prisma/schema.prisma:指定数据源类型(Provider)、生成器(Generator)和定义数据模型(Models)。.env:存储环境变量(如DATABASE_URL)。Prisma CLI 默认只读取此文件,但可以通过系统环境变量覆盖,或在prisma.config.ts中自定义加载逻辑。
1. prisma.config.ts 示例:
typescript
import 'dotenv/config';
import { defineConfig, env } from 'prisma/config';
export default defineConfig({
// 1. 指定 schema 文件位置
schema: 'prisma/schema.prisma',
// 2. 配置迁移文件生成路径
migrations: {
path: 'prisma/migrations',
},
// 3. 配置数据源连接
// 这里使用 env() 函数读取环境变量,实现了配置与代码的分离
datasource: {
url: env('DATABASE_URL'),
},
});
2. prisma/schema.prisma 示例:
prisma
// 指定生成器
generator client {
provider = "prisma-client-js"
}
// 指定数据源类型 (注意:此处不再配置 url)
datasource db {
provider = "mysql"
}
// 定义 User 模型
model User {
id Int @id @default(autoincrement())
email String @unique
name String?
}
2.3 NestJS 模块化集成 (PrismaService)
为了在 NestJS 中高效使用,我们需要创建一个全局 Service 来管理连接生命周期,并在业务模块中注入使用。
步骤 1:创建全局 PrismaService
src/prisma/prisma.service.ts:
typescript
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();
}
}
src/prisma/prisma.module.ts:
typescript
import { Global, Module } from '@nestjs/common';
import { PrismaService } from './prisma.service';
@Global() // 设为全局模块,避免到处 import
@Module({
providers: [PrismaService],
exports: [PrismaService],
})
export class PrismaModule {}
步骤 2:在业务模块中使用
假设有一个 UsersModule,你只需要在 Service 的构造函数中注入 PrismaService 即可。
src/users/users.service.ts:
typescript
import { Injectable } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';
import { User, Prisma } from '@prisma/client'; // 导入生成的类型
@Injectable()
export class UsersService {
constructor(private prisma: PrismaService) {}
async createUser(data: Prisma.UserCreateInput): Promise<User> {
return this.prisma.user.create({
data,
});
}
async findAll() {
return this.prisma.user.findMany();
}
}
2.4 社区方案推荐 (nestjs-prisma)
虽然本文推荐使用官方的手动集成方式以理解底层原理,但在生产环境中,建议选择社区成熟的封装库 nestjs-prisma。
- 特点 :内置了
PrismaModule和PrismaService,开箱即用。 - 优势:提供了日志中间件、异常过滤器等实用工具,省去了样板代码。
- 适用:希望快速搭建、减少样板代码的项目。
三、 Prisma 核心链路
-
配置阶段 (
prisma.config.ts):- 它是总指挥。当你运行
prisma命令时,它首先被加载。 - 它告诉 CLI:去哪找 Schema 定义 (
schema.prisma),去哪找迁移文件 (migrations),以及去哪找数据库连接串 (.env->DATABASE_URL)。
- 它是总指挥。当你运行
-
反向工程阶段 (Introspection, 可选):
- 命令 :
npx prisma db pull - 场景:当你有现存的数据库,或者有人直接修改了数据库结构时。
- 作用 :Prisma 读取数据库的 Schema(表、列、索引),并将它们 反向生成/覆盖 到
prisma/schema.prisma文件中,确保你的 Schema 定义与真实数据库保持同步。
- 命令 :
-
迁移同步阶段 (Migration/Sync):
- 当你运行
npx prisma migrate dev或npx prisma db push时,Prisma 会计算差异并同步到数据库。 - 差异计算方式取决于命令 :
migrate dev:对比schema.prisma和 迁移历史(借助影子库)。生成 SQL 迁移文件并应用到数据库,适合团队协作。db push:对比schema.prisma和 数据库当前结构。忽略迁移历史,根据差异内容,直接将变更应用到数据库,适合快速开发。
- 执行变更 :
migrate dev:执行 本地新生成的 SQL 迁移文件。db push:执行 内存中即时计算出的变更 SQL。- 最终结果都是将模型同步到真实数据库中,创建表、列、索引等。
- 当你运行
-
生成阶段 (CodeGen):
- 当你运行
npx prisma generate时(migrate dev和db push默认都会自动触发此步),Prisma 读取schema.prisma中的model User { ... }。 - 它将这些模型定义"翻译"成 TypeScript 类型定义 (
User,UserCreateInput) 和 JavaScript 查询逻辑。 - 生成的代码被写入到
node_modules/@prisma/client目录中。 - 关键点 :这就是为什么每次修改
schema.prisma后必须 重新运行generate,否则你的代码里拿不到新字段的提示。
- 当你运行
-
运行阶段 (Runtime):
- 当你执行
this.prisma.user.findMany()时,PrismaService实例(已在应用启动时连接数据库)会将 JS 对象转换成 SQL 语句发送给数据库,并将结果反序列化回 JS 对象。
- 当你执行
四、 开发工作流集成
-
代码自动生成 (
postinstall):- 在
package.json中配置"postinstall": "prisma generate"。 - 作用 :每次你(或同事、CI/CD)执行
npm install安装依赖后,会自动触发prisma generate。这样确保node_modules/@prisma/client永远是最新的,你下载完项目就能直接跑,不用担心缺少 Prisma 类型代码。
- 在
-
修改表结构的流程:
- 标准模式 (
migrate dev) :当你修改了schema.prisma后,需手动运行npx prisma migrate dev。- 动作 :生成新的 SQL 迁移文件 -> 执行 SQL 更新数据库 -> 自动运行
prisma generate重新生成 Client 代码。 - 结果 :数据库表结构变更,且
node_modules更新,NestJS 热重载触发,IDE 获得新字段提示。
- 动作 :生成新的 SQL 迁移文件 -> 执行 SQL 更新数据库 -> 自动运行
- Sync 模式 (
db push) :需手动运行npx prisma db push。- 动作 :直接更新数据库结构(无迁移文件) -> 自动运行
prisma generate。 - 场景:适用于快速原型开发。
- 动作 :直接更新数据库结构(无迁移文件) -> 自动运行
- 标准模式 (
-
运行时连接 (
PrismaService):- 我们在
2.3节创建的PrismaService实现了OnModuleInit接口。 - 作用 :当 NestJS 应用启动 (
npm run start:dev) 时,PrismaService会自动调用$connect()建立数据库连接池。应用停止时自动断开。 - 你不需要在每个 Controller 里手动 connect/disconnect,直接注入 Service 使用即可。
- 我们在
五、 深入理解:migrate dev 背后的黑盒机制
Prisma Migrate 的核心逻辑是基于 Shadow Database(影子数据库) 来计算差异的。
当你运行 prisma migrate dev 时,Prisma 会在后台偷偷做这件事:
- 创建影子库:在后台临时创建一个空的数据库(Shadow DB)。
- 重放历史 :把本地
prisma/migrations文件夹里所有的 SQL 文件,按顺序在这个影子库里执行一遍。执行完后,影子库的状态就是 "上一次迁移后的状态" (B)。 - 计算差异 :拿你现在的
schema.prisma(A) 和这个影子库 (B) 对比。 - 生成新迁移 :差异部分生成新的 SQL 文件,放入
prisma/migrations。 - 清理:删掉影子库。
结论:
- Prisma 信任的是你 本地的文件系统 (
prisma/migrations) 作为历史记录的依据。 - 如果你把本地的
migrations文件夹删了,Prisma 就失忆了,下次它就会认为你是从零开始,生成全量迁移。 - 所以,
prisma/migrations文件夹必须提交到 Git,它是项目源代码的重要组成部分。