跳转至

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.tsv7 新增的动态配置文件,替代散落各处的配置
Query Compilerv7 新查询编译器,纯 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 可视化

npx prisma studio  # 打开浏览器数据库管理界面(默认 http://localhost:5555)

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 文档