Prisma ORM 使用:类型安全的数据库客户端
什么是 Prisma ORM
Prisma 是下一代 Node.js 和 TypeScript 的 ORM,提供类型安全的数据库客户端。它彻底解决了手写 SQL 或传统 ORM 带来的类型缺失、查询不直观、迁移困难等问题。Prisma 的核心组件包括:
- Prisma Schema:声明式地定义数据模型、数据库连接和生成器。
- Prisma Migrate:从 Schema 自动生成可预览的 SQL 迁移文件。
- Prisma Client:自动生成且完全类型安全的查询构造器。
本教程将带你从零搭建一个博客数据库示例,掌握 Prisma 在日常开发中的必备技能。
环境准备与项目初始化
在开始之前,请确保已安装 Node.js(建议 18+)和 npm/yarn/pnpm,以及一个可用的数据库(本教程使用 SQLite 以降低门槛,无需额外安装)。
mkdir my-blog && cd my-blog
npm init -y
npm install prisma --save-dev
初始化 Prisma 项目,这会创建 prisma 目录并生成 schema.prisma 文件,同时生成 .env 用于设置数据库连接。
npx prisma init --datasource-provider sqlite
执行后,prisma/schema.prisma 中会包含默认的 datasource 配置:
datasource db {
provider = "sqlite"
url = env("DATABASE_URL")
}
.env 中自动填充 DATABASE_URL="file:./dev.db",表示 SQLite 数据库文件存储在 prisma 目录下。
定义数据模型
打开 prisma/schema.prisma,我们用博客的场景来定义三个模型:User(用户)、Post(文章)和 Comment(评论)。Prisma 使用自己的语法描述表结构、字段类型和关联关系。
model User {
id Int @id @default(autoincrement())
email String @unique
name String?
posts Post[]
comments Comment[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model Post {
id Int @id @default(autoincrement())
title String
content String?
published Boolean @default(false)
author User @relation(fields: [authorId], references: [id])
authorId Int
comments Comment[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model Comment {
id Int @id @default(autoincrement())
text String
post Post @relation(fields: [postId], references: [id])
postId Int
user User @relation(fields: [userId], references: [id])
userId Int
createdAt DateTime @default(now())
}
模型语法要点
@id标记主键,@default(autoincrement())设置自增。@unique确保字段唯一。@default(now())自动记录创建时间,@updatedAt自动更新修改时间。- 字段类型后面的
?表示可选。 - 关联关系通过
User posts Post[]和Post author User成对定义,并使用@relation指定外键。
理解不同数据库的类型映射
Prisma 抽象了底层数据库的类型。比如 String 在 SQLite 中映射为 TEXT,在 PostgreSQL 中为 text。Int 默认映射为 INTEGER。这样你可以在不修改代码的情况下切换数据库。
数据库迁移
定义好模型后,使用 Prisma Migrate 将 Schema 同步到数据库。
npx prisma migrate dev --name init
该命令会:
- 生成 SQL 迁移文件保存在
prisma/migrations目录。 - 将迁移应用到数据库,创建对应的表。
- 自动重新生成 Prisma Client,确保客户端类型与最新 Schema 同步。
每次修改 Schema 后,都要重复执行 npx prisma migrate dev --name 描述 来生成新的迁移。在团队协作中,迁移文件需要提交到版本控制系统。
安装并生成 Prisma Client
在项目中使用 @prisma/client 进行类型安全的数据库操作。
npm install @prisma/client
注意:每次更改 Schema 后,都需要通过 prisma generate(或 migrate dev 自动执行)重新生成客户端。客户端代码生成在 node_modules/.prisma/client,你无需手动引入。
在你的应用中引入 PrismaClient 并创建一个实例。通常我们会创建一个单独的 db.ts 文件来管理实例,避免多次实例化。
// src/db.ts
import { PrismaClient } from '@prisma/client'
const globalForPrisma = globalThis as unknown as {
prisma: PrismaClient | undefined
}
export const db = globalForPrisma.prisma ?? new PrismaClient()
if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = db
这种写法能防止开发模式下因热重载产生多个数据库连接。
基本增删改查操作
创建数据
import { db } from './db'
async function main() {
// 创建用户并同时创建关联的文章
const user = await db.user.create({
data: {
email: 'alice@prisma.io',
name: 'Alice',
posts: {
create: {
title: 'Hello Prisma',
content: '这是我的第一篇文章',
published: true,
},
},
},
})
console.log('创建的用户及文章:', user)
}
main()
.catch((e) => console.error(e))
.finally(async () => await db.$disconnect())
查询数据
Prisma Client 提供了大量类型安全的查询方法。
// 查询所有用户,并包含其文章列表
const usersWithPosts = await db.user.findMany({
include: {
posts: true,
},
})
// 条件筛选:查询已发布的文章
const publishedPosts = await db.post.findMany({
where: {
published: true,
},
})
// 查询单个用户,使用唯一字段
const alice = await db.user.findUnique({
where: {
email: 'alice@prisma.io',
},
})
更新数据
使用 update 或 updateMany 方法。
// 更新一篇文章的标题和发布状态
const updatedPost = await db.post.update({
where: { id: 1 },
data: {
title: 'Prisma 入门完全指南',
published: false,
},
})
// 批量更新
await db.post.updateMany({
where: {
authorId: 1,
},
data: {
published: true,
},
})
删除数据
// 删除单一评论
await db.comment.delete({
where: { id: 1 },
})
// 删除某个用户的所有未发布文章
await db.post.deleteMany({
where: {
authorId: 1,
published: false,
},
})
关系操作与嵌套写入
Prisma 的强大之处在于处理关联数据时依然保持类型安全。
嵌套写入
在创建用户时一并创建文章和评论:
const user = await db.user.create({
data: {
email: 'bob@prisma.io',
name: 'Bob',
posts: {
create: [
{
title: 'GraphQL 与 Prisma',
content: '内容...',
published: true,
comments: {
create: {
text: '很棒的教程!',
user: {
connect: { email: 'alice@prisma.io' }, // 连接已有用户
},
},
},
},
],
},
},
})
关系连接与断开
对已有的记录进行关系的连接(connect)或断开(disconnect):
// 为现有文章添加评论,并连接用户
const comment = await db.comment.create({
data: {
text: '学习到了',
post: { connect: { id: 1 } },
user: { connect: { email: 'alice@prisma.io' } },
},
})
// 断开一个用户与某篇文章的关系(仅移除关联,不删除记录)
await db.post.update({
where: { id: 1 },
data: {
author: { disconnect: true },
},
})
高级查询技巧
过滤与排序
// 查询标题包含“Prisma”的文章,并按创建时间倒序
const posts = await db.post.findMany({
where: {
title: { contains: 'Prisma' },
published: true,
},
orderBy: {
createdAt: 'desc',
},
take: 10, // 分页:取前10条
skip: 0, // 跳过0条
})
数据聚合与分组
Prisma Client 支持聚合函数,如 count、sum、avg 等。
// 统计每个用户的文章数量
const usersWithCount = await db.user.findMany({
select: {
id: true,
name: true,
_count: {
select: { posts: true },
},
},
})
结果会包含 _count: { posts: 5 } 这样的字段。
使用 select 选择需要返回的字段
为避免返回过多数据,使用 select 而非 include 可以精准控制:
const postPreviews = await db.post.findMany({
select: {
id: true,
title: true,
author: {
select: { name: true },
},
},
})
事务处理
使用交互式事务或批量事务来保证数据一致性。
// 交互式事务:可以在回调中执行多个操作
await db.$transaction(async (tx) => {
const user = await tx.user.create({ data: { email: 'charlie@prisma.io' } })
await tx.post.create({
data: {
title: '事务测试',
authorId: user.id,
},
})
})
// 批量事务:直接传递操作数组
await db.$transaction([
db.post.deleteMany({ where: { authorId: 2 } }),
db.user.delete({ where: { id: 2 } }),
])
使用 Prisma Studio 可视化管理数据
Prisma 内置了一个图形化管理界面,可以快速查看和编辑数据。
npx prisma studio
浏览器自动打开,你可以直接增删改查数据,非常适合调试和临时管理。
与真实项目集成示例
假设在一个 Express 或 Fastify 应用中,你可以直接将 Prisma Client 实例挂载到请求上下文中,或通过依赖注入使用。
import express from 'express'
import { db } from './db'
const app = express()
app.use(express.json())
// 获取所有文章
app.get('/posts', async (req, res) => {
const posts = await db.post.findMany({ include: { author: true } })
res.json(posts)
})
// 创建文章
app.post('/posts', async (req, res) => {
const { title, content, authorEmail } = req.body
const post = await db.post.create({
data: {
title,
content,
author: { connect: { email: authorEmail } },
},
})
res.json(post)
})
app.listen(3000)
得益于 Prisma Client 的类型推导,所有查询结果都具有完整的 TypeScript 类型,无需额外声明。
迁移与生产部署最佳实践
在 CI/CD 流程中,你应该:
- 在构建阶段生成 Prisma Client:
npx prisma generate - 在部署前运行数据库迁移:
npx prisma migrate deploy(仅应用已有迁移,不创建新迁移) - 避免在生产环境直接运行
migrate dev,使用migrate deploy更安全。 - 对于无服务环境或边缘运行时,考虑使用 Prisma Accelerate 或连接池管理。
常见问题与调试
- 类型更新不及时:每次修改 Schema 后,运行
npx prisma generate重新生成类型。 - 数据库连接问题:检查
.env中的DATABASE_URL是否正确,确保数据库服务运行正常。 - 迁移冲突:当与他人并行开发时,先拉取最新代码,解决 Schema 冲突后,通过
npx prisma migrate dev生成新的合并迁移。 - 性能问题:使用
include时注意 N+1 查询,Prisma 在 5.x 版本优化了查询策略,但在复杂嵌套时仍可能产生多次查询,使用select或$transaction优化。
结束语
Prisma ORM 以类型安全为核心,让数据库操作回归直觉且高效。通过声明式 Schema、自动生成迁移和强大的客户端 API,你可以在减少样板代码的同时获得完整的开发体验。建议多实践本教程中的示例,逐步将 Prisma 集成到你的实际项目中。