主题
集成 Prisma
以 PostgreSQL 为例
安装与初始化
bash
pnpm add -D prisma
pnpm add @prisma/client @prisma/adapter-pg pg
pnpm prisma init --datasource-provider postgresql --output ../src/generated/prismaprisma 提供 CLI 和迁移能力;@prisma/client 是生成的类型安全客户端;@prisma/adapter-pg 和 pg 用于通过 node-postgres 连接 PostgreSQL。
初始化后主要文件为:
bash
prisma
├── migrations
└── schema.prisma
prisma.config.ts
src
└── generated
└── prisma生成目录是构建产物,不应手写修改;是否提交它以项目的构建和部署策略为准。
环境变量与配置
.env:
md
DATABASE_URL=postgresql://app_user:app_password@localhost:5432/app_db?schema=publicprisma.config.ts:
ts
import 'dotenv/config'
import { defineConfig, env } from 'prisma/config'
export default defineConfig({
schema: 'prisma/schema.prisma',
migrations: {
path: 'prisma/migrations',
},
datasource: {
url: env('DATABASE_URL'),
},
})生产环境不要提交真实连接串。应用应使用独立的数据库业务用户,不使用 PostgreSQL 超级用户。
定义 Schema
prisma/schema.prisma:
prisma
generator client {
provider = "prisma-client"
output = "../src/generated/prisma"
}
datasource db {
provider = "postgresql"
}
model User {
id String @id @default(uuid()) @db.Uuid
name String @db.VarChar(64)
email String @unique @db.VarChar(255)
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(6)
@@map("users")
}Prisma Model 描述应用的数据模型,原生类型属性(如 @db.Uuid)用于控制 PostgreSQL 中的具体列类型。DTO 负责校验外部输入,不要直接将 DTO 或客户端传入字段透传给 Prisma。
生成和执行迁移
开发环境生成并应用迁移:
bash
pnpm prisma migrate dev --name init
pnpm prisma generatemigrate dev 生成 SQL 迁移并在开发数据库执行;每次 schema 变化后都应检查生成的 SQL。生产或预发布环境只执行已提交的迁移:
bash
pnpm prisma migrate deploymigrate deploy 不生成迁移、不重置数据库,也不检查 schema drift。db push 适合原型验证,不能替代多人协作和生产环境中的迁移历史。
可将课程中反复使用的命令收敛到 package.json,避免手写长命令:
json
{
"scripts": {
"db:generate": "prisma generate",
"db:migrate": "prisma migrate dev",
"db:format": "prisma format",
"db:studio": "prisma studio"
}
}db:studio 用于本地查看和编辑开发数据;生产数据的角色提升、修复和迁移应通过受控运维流程完成。
封装 PrismaService
将 Prisma Client 注册为 Nest Provider,业务 Service 通过依赖注入使用它;不要在每个业务模块中各自创建客户端实例。
src/prisma/prisma.service.ts:
ts
import { Injectable, OnModuleDestroy } from '@nestjs/common'
import { PrismaPg } from '@prisma/adapter-pg'
import { PrismaClient } from '../generated/prisma/client.js'
@Injectable()
export class PrismaService extends PrismaClient implements OnModuleDestroy {
constructor() {
const adapter = new PrismaPg({
connectionString: process.env.DATABASE_URL!,
})
super({ adapter })
}
onModuleDestroy() {
return this.$disconnect()
}
}src/prisma/prisma.module.ts:
ts
import { Global, Module } from '@nestjs/common'
import { PrismaService } from './prisma.service'
@Global()
@Module({
providers: [PrismaService],
exports: [PrismaService],
})
export class PrismaModule {}@Global() 可以减少重复导入,但会降低依赖显式性。大型项目可以不使用全局模块,在需要访问数据库的业务模块中显式导入 PrismaModule。
在业务服务中使用
ts
import { Injectable, NotFoundException } from '@nestjs/common'
import { PrismaService } from '../prisma/prisma.service'
@Injectable()
export class UsersService {
constructor(private readonly prisma: PrismaService) {}
findAll() {
return this.prisma.user.findMany({
select: {
id: true,
name: true,
email: true,
},
})
}
async findById(id: string) {
const user = await this.prisma.user.findUnique({ where: { id } })
if (!user) throw new NotFoundException('用户不存在')
return user
}
}查询时显式指定 select、include、where 和排序条件;避免无边界地加载关联数据。列表接口必须限定分页上限,并关注循环查询导致的 N+1 问题。
事务
独立写操作可使用数组事务;需要根据前一步结果决定下一步的业务用例使用交互式事务。
ts
await this.prisma.$transaction(async (tx) => {
const user = await tx.user.create({
data: { name: 'vfan', email: 'vfan@example.com' },
})
return user
})事务回调内只使用 tx。事务应尽量短,不要在其中执行网络请求、文件 I/O 或长时间计算。
