Prisma 数据库 ORM¶
一句话概述:Prisma 是 Node.js/TypeScript 最流行的 ORM,v7 去掉了 Rust 引擎改用纯 TypeScript,查询快 3 倍、包体积小 90%,用声明式 Schema 定义数据库结构,自动生成类型安全的查询客户端。
核心知识点¶
| 概念 | 白话解释 |
|---|---|
| ORM | 对象关系映射,用代码操作数据库,不用写 SQL |
| Prisma Schema | .prisma 文件,用来定义数据库表结构和关系 |
| Prisma Client | 根据 Schema 自动生成的查询工具,带完整类型提示 |
| Prisma Migrate | 数据库迁移工具,Schema 改了自动生成 SQL 迁移文件 |
| Prisma Studio | 可视化数据库管理界面,在浏览器里查看和编辑数据 |
prisma.config.ts | v7 新增的动态配置文件,替代散落各处的配置 |
| Query Compiler | v7 新查询编译器,纯 TypeScript 实现,不再依赖 Rust |
安装配置¶
初始化项目¶
# 新建项目
mkdir my-project && cd my-project # 创建并进入项目目录
npm init -y # 初始化 package.json
npm install typescript ts-node @types/node --save-dev # 安装 TypeScript 开发依赖
npx tsc --init # 生成 tsconfig.json
# 安装 Prisma
npm install prisma --save-dev # 安装 Prisma CLI(开发依赖)
npm install @prisma/client # 安装 Prisma Client(运行时依赖)
# 初始化 Prisma
npx prisma init # 生成 prisma/schema.prisma 和 .env 文件
配置数据库连接¶
# .env 文件 - 数据库连接字符串
# PostgreSQL
DATABASE_URL="postgresql://用户名:密码@localhost:5432/数据库名?schema=public"
# MySQL
DATABASE_URL="mysql://用户名:密码@localhost:3306/数据库名"
# SQLite(本地文件数据库,学习用最方便)
DATABASE_URL="file:./dev.db"
定义 Schema¶
// prisma/schema.prisma - 数据库模型定义
generator client {
provider = "prisma-client-js" // 生成 JavaScript/TypeScript 客户端
}
datasource db {
provider = "postgresql" // 数据库类型:postgresql/mysql/sqlite
url = env("DATABASE_URL") // 从环境变量读取连接地址
}
// 用户表
model User {
id Int @id @default(autoincrement()) // 主键,自增
email String @unique // 唯一约束,不能重复
name String? // ? 表示可以为空(可选字段)
age Int @default(0) // 默认值为 0
posts Post[] // 一对多关系:一个用户有多篇文章
profile Profile? // 一对一关系:一个用户有一个资料
createdAt DateTime @default(now()) // 创建时间,默认当前时间
updatedAt DateTime @updatedAt // 更新时间,自动更新
@@index([email]) // 给 email 字段加索引,查询更快
@@map("users") // 映射到数据库表名 "users"
}
// 文章表
model Post {
id Int @id @default(autoincrement())
title String // 标题
content String? // 内容,可为空
published Boolean @default(false) // 是否发布,默认未发布
author User @relation(fields: [authorId], references: [id]) // 关联用户表
authorId Int // 外键字段
tags Tag[] // 多对多关系
createdAt DateTime @default(now())
}
// 资料表(一对一)
model Profile {
id Int @id @default(autoincrement())
bio String?
user User @relation(fields: [userId], references: [id])
userId Int @unique // 一对一所以加 unique
}
// 标签表(多对多)
model Tag {
id Int @id @default(autoincrement())
name String @unique
posts Post[] // 多对多关系,Prisma 自动创建中间表
}
同步数据库¶
# 开发时:根据 Schema 创建/更新数据库表
npx prisma db push # 直接推送 Schema 变更到数据库(开发用)
# 生产时:生成迁移文件
npx prisma migrate dev --name init # 生成并执行迁移,命名为 "init"
npx prisma migrate deploy # 生产环境执行迁移
# 生成 Prisma Client
npx prisma generate # 根据 Schema 生成类型安全的客户端代码
基本使用¶
CRUD 操作¶
// src/index.ts
import { PrismaClient } from '@prisma/client' // 导入 Prisma Client
const prisma = new PrismaClient() // 创建客户端实例
async function main() {
// ===== 创建(Create) =====
const user = await prisma.user.create({ // 创建一个用户
data: {
email: 'zhang@example.com', // 必填字段
name: '张三', // 可选字段
posts: { // 同时创建关联的文章
create: [
{ title: '第一篇文章', content: '内容...' },
{ title: '第二篇文章', published: true },
],
},
},
})
console.log('创建用户:', user)
// ===== 查询(Read) =====
// 查询单个
const foundUser = await prisma.user.findUnique({ // 按唯一字段查找
where: { email: 'zhang@example.com' }, // 查询条件
})
// 查询多个
const allUsers = await prisma.user.findMany({ // 查找多条记录
where: {
name: { contains: '张' }, // 名字包含"张"
},
orderBy: { createdAt: 'desc' }, // 按创建时间倒序
take: 10, // 只取前 10 条(分页)
skip: 0, // 跳过 0 条
include: { posts: true }, // 同时查出关联的文章
})
// ===== 更新(Update) =====
const updated = await prisma.user.update({ // 更新用户
where: { email: 'zhang@example.com' }, // 找到要更新的记录
data: { name: '张三丰' }, // 要更新的字段
})
// ===== 删除(Delete) =====
const deleted = await prisma.user.delete({ // 删除用户
where: { email: 'zhang@example.com' },
})
}
main()
.catch(console.error) // 捕获错误
.finally(() => prisma.$disconnect()) // 最后断开连接
常用查询条件¶
// 条件过滤
const users = await prisma.user.findMany({
where: {
AND: [ // 多个条件同时满足
{ age: { gte: 18 } }, // gte: 大于等于 18
{ age: { lte: 60 } }, // lte: 小于等于 60
],
OR: [ // 多个条件满足其一
{ email: { endsWith: '@gmail.com' } }, // 以 @gmail.com 结尾
{ email: { endsWith: '@qq.com' } },
],
NOT: { name: null }, // 排除 name 为空的
name: { startsWith: '张' }, // 以"张"开头
},
select: { // 只查部分字段(减少数据传输)
id: true,
name: true,
email: true,
_count: { select: { posts: true } }, // 统计文章数
},
})
高级用法¶
事务操作¶
// 方式一:交互式事务(推荐)
const result = await prisma.$transaction(async (tx) => { // tx 是事务内的 prisma 实例
const user = await tx.user.create({ // 在事务内创建用户
data: { email: 'li@example.com', name: '李四' },
})
const post = await tx.post.create({ // 在事务内创建文章
data: { title: '文章', authorId: user.id },
})
return { user, post } // 全部成功才提交,任一失败全部回滚
})
// 方式二:批量事务
const [users, posts] = await prisma.$transaction([ // 数组里的操作要么全成功要么全失败
prisma.user.findMany(),
prisma.post.findMany(),
])
原生 SQL 查询¶
// 当 Prisma API 不够用时,可以写原生 SQL
const result = await prisma.$queryRaw`
SELECT u.name, COUNT(p.id) as post_count
FROM users u
LEFT JOIN posts p ON u.id = p."authorId"
GROUP BY u.name
HAVING COUNT(p.id) > ${minCount} -- 参数化查询,防 SQL 注入
`
中间件(日志、审计)¶
// 给所有查询加日志
prisma.$use(async (params, next) => { // 中间件函数
const before = Date.now() // 记录开始时间
const result = await next(params) // 执行实际查询
const after = Date.now() // 记录结束时间
console.log(`${params.model}.${params.action} 耗时 ${after - before}ms`) // 打印耗时
return result
})
// 软删除中间件
prisma.$use(async (params, next) => {
if (params.action === 'delete') { // 拦截删除操作
params.action = 'update' // 改为更新
params.args.data = { deletedAt: new Date() } // 设置删除时间而非真正删除
}
return next(params)
})
Prisma Studio 可视化¶
v7 动态配置¶
// prisma.config.ts - v7 新增的统一配置文件
import path from 'node:path'
export default {
earlyAccess: true, // 启用早期功能
schema: path.join(__dirname, 'prisma', 'schema.prisma'), // Schema 路径
seed: path.join(__dirname, 'prisma', 'seed.ts'), // 种子脚本路径
}
常见报错¶
| 报错信息 | 原因 | 解决方案 |
|---|---|---|
PrismaClientInitializationError | 数据库连不上 | 检查 .env 里的 DATABASE_URL 是否正确 |
P2002: Unique constraint failed | 唯一字段重复了 | 数据已存在,用 upsert 代替 create |
P2025: Record not found | 记录不存在 | 先查再改,或用 updateMany |
PrismaClientKnownRequestError | 查询有问题 | 检查 where 条件的字段名和类型 |
| 类型报错没提示 | Client 没重新生成 | 运行 npx prisma generate |
| 迁移冲突 | 多人开发迁移冲突 | npx prisma migrate resolve 解决 |
速查表¶
# CLI 命令
npx prisma init # 初始化 Prisma 项目
npx prisma generate # 生成 Prisma Client
npx prisma db push # 推送 Schema 到数据库(开发)
npx prisma migrate dev --name xxx # 创建并执行迁移(开发)
npx prisma migrate deploy # 执行迁移(生产)
npx prisma migrate reset # 重置数据库(删除所有数据!)
npx prisma studio # 打开可视化管理界面
npx prisma db seed # 运行种子脚本填充数据
npx prisma format # 格式化 Schema 文件
npx prisma validate # 验证 Schema 是否正确
# Schema 字段类型
Int # 整数
String # 字符串
Boolean # 布尔值
Float # 浮点数
DateTime # 日期时间
Json # JSON 对象
Bytes # 二进制数据
BigInt # 大整数
# Schema 字段修饰符
@id # 主键
@unique # 唯一约束
@default(value) # 默认值
@default(autoincrement()) # 自增
@default(now()) # 当前时间
@updatedAt # 自动更新时间
@relation # 定义关系
? # 可选字段(可为 null)
[] # 一对多/多对多关系
# 查询过滤条件
equals / not # 等于/不等于
in / notIn # 在列表中/不在列表中
lt / lte / gt / gte # 小于/小于等于/大于/大于等于
contains / startsWith / endsWith # 包含/开头/结尾
AND / OR / NOT # 逻辑组合
参考:Prisma 官网 | Prisma 7 发布 | Prisma 文档