Prisma ORM 指南
概览
Prisma 是一个用于 Node.js 和 TypeScript 的下一代 TypeScript ORM,它通过自动生成的类型和查询使数据库访问变得简单。这项技能涵盖了模式定义、迁移、查询模式、事务和性能优化。
前提条件
- 理解 TypeScript
- 了解数据库概念(SQL、关系、索引)
- 熟悉 Node.js 和 npm
- 基本了解数据库迁移
核心概念
Prisma 架构
- Prisma 客户端:自动生成的类型安全数据库客户端
- Prisma 模式:声明式模式定义语言
- Prisma 迁移:数据库迁移工具
- Prisma 工作室:可视化数据库 IDE
- 类型安全:自动生成的 TypeScript 类型
模式设计哲学
Prisma 的模式允许:
- 声明式模型:使用 Prisma 模式定义模型
- 类型安全:自动生成的 TypeScript 类型
- 关系:定义模型之间的关系
- 约束:数据库级约束
- 索引:定义索引以提高性能
实施指南
模式定义
基本模型
// prisma/schema.prisma
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model User {
id String @id @default(cuid())
email String @unique
name String?
password String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
关系
// prisma/schema.prisma
model User {
id String @id @default(cuid())
email String @unique
name String?
posts Post[]
profile Profile?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model Post {
id String @id @default(cuid())
title String
content String?
published Boolean @default(false)
authorId String
author User @relation(fields: [authorId], references: [id])
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model Profile {
id String @id @default(cuid())
bio String?
userId String @unique
user User @relation(fields: [userId], references: [id])
}
枚举
// prisma/schema.prisma
enum Role {
USER
ADMIN
MODERATOR
}
enum Status {
DRAFT
PUBLISHED
ARCHIVED
}
model User {
id String @id @default(cuid())
email String @unique
role Role @default(USER)
status Status @default(DRAFT)
}
索引
// prisma/schema.prisma
model User {
id String @id @default(cuid())
email String @unique
name String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([email, name])
@@index([createdAt])
@@index([updatedAt])
}
model Post {
id String @id @default(cuid())
title String
published Boolean @default(false)
authorId String
createdAt DateTime @default(now)
@@index([authorId, published])
@@index([createdAt])
}
约束
// prisma/schema.prisma
model User {
id String @id @default(cuid())
email String @unique
age Int
@@map("users")
@@index([email], name: "user_email_name_idx")
}
model Product {
id String @id @default(cuid())
name String
price Float
quantity Int
@@check: "price >= 0"
@@check: "quantity >= 0"
}
迁移工作流
创建迁移
# 创建一个新的迁移
npx prisma migrate dev --name add_user_profile
# 创建迁移,没有名称(自动生成)
npx prisma migrate dev
将迁移应用到生产环境
# 生成迁移 SQL 进行审核
npx prisma migrate dev --create-only --name add_user_profile
# 将迁移部署到生产环境
npx prisma migrate deploy
# 部署特定迁移
npx prisma migrate deploy --name add_user_profile
迁移历史
# 查看迁移历史
npx prisma migrate status
# 解决迁移冲突
npx prisma migrate resolve --applied "add_user_profile"
# 重置数据库并重新应用迁移
npx prisma migrate reset
查询模式
CRUD 操作
// 创建
const user = await prisma.user.create({
data: {
name: 'John Doe',
email: 'john@example.com',
password: 'hashed_password'
}
})
// 读取 - 查找多个
const users = await prisma.user.findMany({
orderBy: { createdAt: 'desc' }
})
// 读取 - 查找一个
const user = await prisma.user.findUnique({
where: { email: 'john@example.com' }
})
// 更新
const updatedUser = await prisma.user.update({
where: { id: userId },
data: { name: 'John Smith' }
})
// 删除
await prisma.user.delete({
where: { id: userId }
})
// 删除多个
await prisma.user.deleteMany({
where: { status: 'inactive' }
})
过滤
// 字符串过滤器
const users = await prisma.user.findMany({
where: {
OR: [
{ email: { contains: 'example.com' } },
{ name: { startsWith: 'John' } }
]
}
})
// 数字过滤器
const products = await prisma.product.findMany({
where: {
AND: [
{ price: { gte: 10, lte: 100 } },
{ quantity: { gt: 0 } }
]
}
})
// 日期过滤器
const posts = await prisma.post.findMany({
where: {
createdAt: {
gte: new Date('2024-01-01'),
lte: new Date('2024-12-31')
}
}
})
// 数组过滤器
const users = await prisma.user.findMany({
where: {
tags: { hasEvery: ['admin', 'moderator'] }
}
})
// 空过滤器
const usersWithProfile = await prisma.user.findMany({
where: {
profile: { isNot: null }
}
})
关系
// 带嵌套关系的创建
const post = await prisma.post.create({
data: {
title: 'New Post',
content: 'Post content',
author: {
connect: {
id: userId,
},
create: {
name: 'Author Name',
email: 'author@example.com'
}
}
}
})
// 在查询中包含关系
const postWithAuthor = await prisma.post.findUnique({
where: { id: postId },
include: {
author: true
}
})
// 包含多个关系
const postWithRelations = await prisma.post.findUnique({
where: { id: postId },
include: {
author: {
include: {
profile: true
}
},
comments: true
}
})
分页
// 跳过和限制
const page = 1
const pageSize = 10
const users = await prisma.user.findMany({
skip: (page - 1) * pageSize,
take: pageSize,
orderBy: { createdAt: 'desc' }
})
// 获取分页的总数
const total = await prisma.user.count()
const totalPages = Math.ceil(total / pageSize)
排序
// 单字段排序
const users = await prisma.user.findMany({
orderBy: { name: 'asc' }
})
// 多字段排序
const users = await prisma.user.findMany({
orderBy: [
{ createdAt: 'desc' },
{ name: 'asc' }
]
})
事务
基本事务
const session = await prisma.$transaction(async (tx) => {
const user = await tx.user.create({
data: { name: 'John' }
})
const post = await tx.post.create({
data: {
title: 'Post 1',
authorId: user.id
}
})
await tx.post.create({
data: {
title: 'Post 2',
authorId: user.id
}
})
})
带多个操作的事务
const session = await prisma.$transaction(async (tx) => {
const user = await tx.user.create({
data: { name: 'John' }
})
await tx.post.createMany({
data: [
{ title: 'Post 1', authorId: user.id },
{ title: 'Post 2', authorId: user.id }
]
})
await tx.user.update({
where: { id: user.id },
data: { postCount: 2 }
})
})
带错误处理的事务
async function transferFunds(fromId: string, toId: string, amount: number) {
const session = await prisma.$transaction(async (tx) => {
const [fromUser, toUser] = await tx.user.findMany({
where: { id: { in: [fromId, toId] } }
})
if (!fromUser || !toUser) {
throw new Error('User not found')
}
if (fromUser.balance < amount) {
throw new Error('Insufficient funds')
}
await tx.user.update({
where: { id: fromId },
data: { balance: { decrement: amount } }
})
await tx.user.update({
where: { id: toId },
data: { balance: { increment: amount } }
})
await tx.transaction.create({
data: {
fromId,
toId,
amount,
type: 'transfer'
}
})
})
}
交互式事务
async function createUserWithProfile(userData: any, profileData: any) {
return await prisma.$transaction(async (tx) => {
const user = await tx.user.create({
data: userData
})
const profile = await tx.profile.create({
data: {
...profileData,
userId: user.id
}
})
return { user, profile }
})
}
性能优化
查询优化
// 好:只选择需要的字段
const users = await prisma.user.findMany({
select: {
id: true,
email: true,
name: true
}
})
// 坏:获取所有字段
const users = await prisma.user.findMany()
连接池
// prisma/schema.prisma
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
// 连接池设置
connection_limit = 10
pool_timeout = 20
}
批量操作
// 好:使用 createMany 进行批量插入
const users = await prisma.user.createMany({
data: [
{ name: 'User 1', email: 'user1@example.com' },
{ name: 'User 2', email: 'user2@example.com' },
{ name: 'User 3', email: 'user3@example.com' }
]
})
// 坏:多个单独创建
for (const userData of userDataArray) {
await prisma.user.create({ data: userData })
}
最佳实践
-
模式设计
- 使用适合您的数据的适当字段类型
- 正确定义关系
- 为频繁查询的字段添加索引
- 使用枚举表示固定值集
- 为数据完整性添加约束
-
迁移
- 在部署前始终审查生成的迁移
- 使用描述性的迁移名称
- 先在开发中测试迁移
- 对数据迁移使用事务
- 保持迁移历史清晰
-
查询优化
- 只选择需要的字段
- 使用适当的索引
- 对大型数据集使用分页
- 尽可能使用 findFirst 而不是 findMany
- 使用 includes 避免 N+1 查询问题
-
事务
- 对多步骤操作使用事务
- 保持事务简短
- 正确处理错误并回滚
- 使用适当的隔离级别
-
错误处理
- 优雅地处理已知的 Prisma 错误
- 实施适当的错误日志记录
- 使用类型安全的错误处理
- 提供有意义的错误消息
-
测试
- 为关键操作编写单元测试
- 彻底测试迁移
- 使用 Prisma 的模拟客户端进行测试
- 测试边缘情况和错误场景