arrow_back返回文章列表
技术

第 3 篇:Prisma Schema 全面拆解

在第 2 篇里,我们已经用 Prisma 7 + Next.js 16.2.2 + PostgreSQL + pnpm 搭好了一个项目,并且在 prisma/schema.prisma 中定义了一个最小可用的 User 模型。

这一篇的目标是:把这份 schema 从上到下拆开讲清楚——datasource、generator、model、enum、各种属性都是什么、怎么用。看完之后,你应该能:


1. 回顾一下我们当前的 schema.prisma

先看一下目前项目里的 prisma/schema.prisma(第 2 篇的版本):

generator client {
  provider = "prisma-client"
  output   = "../generated/prisma"
}

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

model User {
  id        Int      @id @default(autoincrement())
  name      String
  email     String   @unique
  createdAt DateTime @default(now())
}

Prisma 官方文档会告诉你:Prisma Schema 一共就三块

这一篇就是沿着这三个部分展开。


2. datasource:告诉 Prisma “去哪儿连库”

2.1 我们当前的 datasource 长什么样?

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

这段配置的作用是:

结合第 2 篇 .env 的写法:

DATABASE_URL="postgresql://prisma:prisma@localhost:5432/next_prisma_demo?schema=public"

完整含义就是:

2.2 datasource 还能改哪些东西?

在 Prisma Schema Reference 里,datasource 除了 providerurl,还有一些更高级的配置,例如 relations 模式等,不过你目前用 Postgres,常见配置基本只用这两个就够了。

此时你要记住的一句话是:

Prisma 只通过 datasource 知道“你在连什么数据库”,并据此决定支持哪些类型、特性。


3. generator:告诉 Prisma “生成什么客户端”

3.1 Prisma 7 推荐的 generator 写法

在 Prisma 7 中,我们在 schema 里写的是:

generator client {
  provider = "prisma-client"
  output   = "../generated/prisma"
}

官方文档说明:对于 prisma-client generator,output 是必填,用来告诉 Prisma 把生成的 Client 放到哪里。

配合第 2 篇的项目结构,这代表:

这就是我们在 lib/prisma.ts 里这样导入的原因:

import { PrismaClient } from '../generated/prisma'

3.2 generator 的几个关键点

基于 Prisma 7 官方文档,generator 有这些要点值得记:

如果你以后在 monorepo / 多包结构中使用 Prisma,output 会更重要;当前这个单包项目,用默认的 generated/prisma 路径已经足够清晰。


4. data model:User 模型里的每一部分

现在进入最重要部分:数据模型。官方文档称这一部分为 Data Model Definition,包括 model、enum、attributes 等。

我们当前的 User 模型是:

model User {
  id        Int      @id @default(autoincrement())
  name      String
  email     String   @unique
  createdAt DateTime @default(now())
}

可以先把它拆成几个维度来理解:

我们先只看“标量 + 基本属性”,关系下一篇专讲。

4.1 字段结构:名字 + 类型 + 可选修饰

email 行为例:

email     String   @unique

拆开就是:

再看 id 行:

id        Int      @id @default(autoincrement())

这正好印证了官方对 model 字段的定义:字段 = 名字 + 类型 + 可选修饰符(?、[])+ 属性

4.2 标量类型(Scalar Types)

Prisma 支持一组标量类型,官方文档列了常用这些:

以及一些 provider-specific 的原生类型映射(通过 @db.* 属性),比如 @db.VarChar(255)@db.Decimal(10,2) 等。

如果我们要把 User 模型稍微扩展一下,例如加一个余额字段,可以写成:

model User {
  id        Int      @id @default(autoincrement())
  name      String
  email     String   @unique
  balance   Decimal  @default(0)
  createdAt DateTime @default(now())
}

具体映射到 Postgres 时,可以再加原生类型属性,例如:

balance   Decimal  @db.Decimal(10, 2) @default(0)

这样 Prisma 就知道应该在数据库里用 DECIMAL(10,2)。

4.3 可空与数组

Prisma 中类型后面的 ?[] 是两个非常常用的修饰符:

比如我们想让 name 变成可选字段:

name String?

或者给 User 加一个字符串数组字段 tags

tags String[]

目前我们的 User 是故意保持简单:所有字段都必填,暂不使用数组;后面做关系建模时,你会看到类似 posts Post[] 这样的数组类型字段。


5. 字段属性:用 @xxx 给字段加“约束和语义”

Prisma 用所谓的“属性(Attribute)”来描述字段的约束和语义,写在字段后面,以 @ 开头。

User 里我们已经用到了这些:

5.1 @id:主键

id Int @id @default(autoincrement())

Prisma 文档说明:ID 字段唯一标识模型中的每条记录,一个 model 可以有一个 @id 或一个 @@id(复合主键)。

我们这里使用的是最常见的“单字段整数主键 + 自增”。

5.2 @default(...):默认值

@default() 可以给字段指定默认值或默认函数。常见组合:

User 中,我们用的是:

id        Int      @id @default(autoincrement())
createdAt DateTime @default(now())

对应数据库层面就是:id 用序列/自增,createdAt 默认填入插入时的时间。

5.3 @unique:唯一约束

email String @unique

表示 User.email 在数据库中必须唯一。Prisma Migrate 会为此创建一个唯一索引。

你以后在业务里尝试插入重复 email 时,会收到类似 “P2002 Unique constraint failed” 的错误,这可以用来做用户注册冲突校验。

5.4 @updatedAt:自动更新时间(可选补充)

虽然当前 User 模型里还没用,但你基本一定会用到的另一个常见属性是 @updatedAt

updatedAt DateTime @updatedAt

你可以考虑在当前项目里把 User 模型升级为:

model User {
  id        Int      @id @default(autoincrement())
  name      String
  email     String   @unique
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
}

6. 模型属性(@@xxx):在模型层面加规则

模型属性写在 model 块的底部,不属于任何字段,以 @@ 开头,常见的有:

6.1 @@map:自定义表名

目前不一定用得上,但理解一下很有用。比如你想在代码里叫 User,数据库里表名叫 users

model User {
  id        Int      @id @default(autoincrement())
  name      String
  email     String   @unique
  createdAt DateTime @default(now())

  @@map("users")
}

这样:

适合“老数据库接入 + 新代码”场景。

6.2 @@index:为常用查询加索引

假设你经常按 createdAt 查询最新用户,可以加一个索引:

model User {
  id        Int      @id @default(autoincrement())
  name      String
  email     String   @unique
  createdAt DateTime @default(now())

  @@index([createdAt])
}

或者你有一个组合查询需求,比如常按 (email, createdAt) 组合过滤,也可以这样写:

@@index([email, createdAt])

Prisma Migrate 会生成对应的索引 SQL,提高查询性能。


7. enum:固定值集合的更好选择(预备给 User 增强)

当字段取值是一个有限集合,比如用户角色、订单状态、任务优先级等,不建议用随意字符串,官方推荐用 enum。

7.1 为 User 增加 Role 枚举字段

我们可以在当前 schema 上加一个简单枚举:

enum Role {
  USER
  ADMIN
}

然后在 User 模型中增加一个 role 字段:

model User {
  id        Int      @id @default(autoincrement())
  name      String
  email     String   @unique
  role      Role     @default(USER)
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
}

好处是:

在代码里会变成类似这样的体验:

const user = await prisma.user.create({
  data: {
    name: 'Alice',
    email: 'alice@example.com',
    role: 'ADMIN', // 有 TS 类型提示
  },
})

Prisma 官方 Schema 文档建议:凡是“固定集合”的字符串字段,能用 enum 就用 enum,这可以减少拼写错误和魔法字符串。


8. 关于关系(@relation):在下一篇用博客系统专讲

在第 2 篇和本篇中,我们刻意只用了单表 User 模型,目的是把 Schema 语言的基础部分讲清楚。关系建模(User–Post–Comment 这种)会在下一篇里用“博客系统”的完整例子集中拆解。

这里先给一个概念图,和官方文档里的模型类似:

model User {
  id      Int    @id @default(autoincrement())
  name    String
  email   String @unique
  posts   Post[]
}

model Post {
  id        Int      @id @default(autoincrement())
  title     String
  content   String?
  authorId  Int
  author    User     @relation(fields: [authorId], references: [id])
  createdAt DateTime @default(now())
}

你可以先记住这些点即可:

下一篇会专门围绕“博客系统 User / Post / Comment”来讲一对一、一对多、多对多和嵌套写入等内容。


9. 和当前项目的实践连接:你现在能做什么?

结合第 2 篇和本篇,你现在已经可以对当前项目的 schema.prisma 做一些稳当的增强,例如:

每次修改 schema 后,你都可以:

  1. pnpm prisma migrate dev --name something 生成迁移并同步本地库;

  2. 使用 pnpm prisma studio 打开 UI 看看字段和数据是否如预期;

  3. 在 Next.js 里通过 prisma.user.findMany() 等查询新字段。

当你之后把 Prisma MCP 接进来时,AI 大部分也就是帮你改这个 schema 和跑这些命令,所以理解 schema 的语义,是你能“审查 AI 输出”的前提


10. 小结:这一篇你需要真正掌握的点

快速过一下,如果满足这些,说明你已经掌握了 Prisma Schema 的基础语法: