HeyBinyang

第 3 篇:Prisma Schema 全面拆解

·技术·visibility4 阅读 ·开发

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

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

  • 自己读懂 / 修改现在项目里的 schema.prisma

  • 按业务需求扩展 User 模型;

  • 为后面关系建模、迁移策略打好语法基础。


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 一共就三块

  • datasource:数据源配置;

  • generator:生成器配置;

  • data model:数据模型定义(model、enum 等)。

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


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

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

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

这段配置的作用是:

  • 名字叫 db(可以改,但没必要);

  • provider = "postgresql":告诉 Prisma 使用 PostgreSQL 连接器;

  • url = env("DATABASE_URL"):从环境变量读取连接串。

结合第 2 篇 .env 的写法:

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

完整含义就是:

  • 协议:postgresql://

  • 用户/密码:prisma:prisma

  • host / port:localhost:5432

  • 数据库名:next_prisma_demo

  • schema:public(Postgres 的 schema 概念,不是 Prisma Schema 文件)。

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 篇的项目结构,这代表:

  • schema.prismaprisma/

  • output = "../generated/prisma"

  • 所以 Client 会生成到项目根目录的 generated/prisma 目录里。

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

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

3.2 generator 的几个关键点

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

  • provider

  • "prisma-client":生成 Prisma Client(现代、ESM-first 的方式);

  • output

  • 必填;

  • 决定生成的 Client 代码路径;

  • 用相对 schema 的路径,例如 ../generated/prisma

如果你以后在 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())
}

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

  • 每一行是一个字段;

  • 每个字段有“名字 + 类型 + 可选修饰符 + 属性”;

  • 类型可以是标量(String、Int 等)或关系(比如后面会有 posts Post[])。

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

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

email 行为例:

email     String   @unique

拆开就是:

  • 字段名:email(在 Prisma Client 中就是 user.email);

  • 字段类型:String

  • 属性:@unique(字段级唯一约束)。

再看 id 行:

id        Int      @id @default(autoincrement())
  • 名字:id

  • 类型:Int

  • 属性:@id 表示主键;

  • @default(autoincrement()):默认值来自自增序列。

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

4.2 标量类型(Scalar Types)

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

  • String

  • Boolean

  • Int

  • BigInt

  • Float

  • Decimal

  • DateTime

  • Json

  • Bytes

以及一些 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 中类型后面的 ?[] 是两个非常常用的修饰符:

  • ?:表示该字段可以为 NULL;

  • []:表示这是一个数组类型(或关系数组)。

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

name String?

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

tags String[]

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


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

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

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

  • @id

  • @default(...)

  • @unique[]

5.1 @id:主键

id Int @id @default(autoincrement())

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

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

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

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

  • 整数自增:@default(autoincrement())

  • 当前时间:@default(now())

  • 固定值:@default("guest")@default(false) 等;

  • UUID:@default(uuid())

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
  • Prisma 每次更新这条记录时,会自动把 updatedAt 写成当前时间;

  • 不需要你在代码里手动赋值。

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

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

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

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

  • @@map("table_name"):映射表名;

  • @@unique([field1, field2]):复合唯一;

  • @@index([field1, field2]):索引;

  • @@id([field1, field2]):复合主键。

6.1 @@map:自定义表名

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

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

  @@map("users")
}

这样:

  • Prisma Client 中照样用 prisma.user.findMany()

  • 数据库里实际操作的是 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
}

好处是:

  • 在 schema 层面锁死取值,只能是 USERADMIN

  • Prisma Client 的 TS 类型也会反映这个枚举;

  • 数据库层面也会用枚举/受限类型来保证合法性(取决于 provider)。

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

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())
}

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

  • 关系字段:例如 posts Post[],类型是 另一个 model,而不是标量;

  • 外键字段:例如 authorId Int

  • @relation(fields: [...], references: [...]):指明外键关系。

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


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

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

  • UserupdatedAt 字段:

    updatedAt DateTime @updatedAt
    
  • 定义 Role 枚举并为 User 增加 role 字段;

  • 为常用查询字段加索引,例如 createdAt

  • @@map 兼容已有数据库中的表名规则(如果你接老库)。

每次修改 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 的基础语法:

  • 看得懂现在 schema.prisma 里的三块:generator / datasource / model User

  • 知道 generator client 在 Prisma 7 中用 provider = "prisma-client" + output 配置;

  • 知道 datasource 的 provider = "postgresql"url = env("DATABASE_URL") 分别含义是什么;

  • 能说出 User 模型每个字段的类型、可空性和属性语义(@id@default@unique@updatedAt);

  • 大致知道 enum 在什么场景下比普通字符串更合适(角色、状态等固定集合);

  • 能用 @@index@@map 为模型添加索引和表名映射。

分享