Prisma ORM 使用:类型安全的数据库客户端

FreeGuideOnline 最新 2026-06-15

什么是 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 中为 textInt 默认映射为 INTEGER。这样你可以在不修改代码的情况下切换数据库。

数据库迁移

定义好模型后,使用 Prisma Migrate 将 Schema 同步到数据库。

npx prisma migrate dev --name init

该命令会:

  1. 生成 SQL 迁移文件保存在 prisma/migrations 目录。
  2. 将迁移应用到数据库,创建对应的表。
  3. 自动重新生成 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',
  },
})

更新数据

使用 updateupdateMany 方法。

// 更新一篇文章的标题和发布状态
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 流程中,你应该:

  1. 在构建阶段生成 Prisma Client:npx prisma generate
  2. 在部署前运行数据库迁移:npx prisma migrate deploy (仅应用已有迁移,不创建新迁移)
  3. 避免在生产环境直接运行 migrate dev,使用 migrate deploy 更安全。
  4. 对于无服务环境或边缘运行时,考虑使用 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 集成到你的实际项目中。