模型关系:一对一、一对多、多对多怎么定义
关系的本质
在关系数据库里,表与表之间的关系通过外键实现。Prisma Schema 把这些关系翻译成更接近对象模型的声明式写法,同时在 Prisma Client 生成 include、select、嵌套写入等类型安全的查询 API。
一对多(最常见)
场景:一个作者可以有多篇文章,每篇文章只有一个作者。
model User {
id Int @id @default(autoincrement())
email String @unique
posts Post[] // Prisma 级关联字段
}
model Post {
id Int @id @default(autoincrement())
title String
author User? @relation(fields: [authorId], references: [id])
authorId Int? // 真实外键列
}
关键点:
User.posts Post[]是 Prisma 级的关联字段,数据库里不会多一列Post.author User?+Post.authorId Int?+@relation是真实的外键定义@relation说:Post.authorId这个外键列引用User.id主键
Prisma Client 查询时可以这样用:
// 查询用户并包含他的文章
const user = await prisma.user.findUnique({
where: { id: 1 },
include: { posts: true },
})
// 嵌套写入:创建用户同时创建文章
await prisma.user.create({
data: {
email: 'alice@prisma.io',
posts: { create: { title: '我的第一篇文章' } },
},
})
一对一
场景:每个用户有且只有一份个人档案,每份档案只属于一个用户。
model User {
id Int @id @default(autoincrement())
email String @unique
profile Profile?
}
model Profile {
id Int @id @default(autoincrement())
bio String?
userId Int @unique
user User @relation(fields: [userId], references: [id])
}
与一对多的区别是:
- 双方都是
?或单边必填(user User没有?) - 外键列
userId上必须有@unique——保证不会有两份档案指向同一个用户
多对多
场景:一篇文章可以有多个标签,一个标签可以贴在多篇文章上。
Prisma 支持两种方式:隐式多对多(自动创建中间表)和 显式多对多(手动定义中间表 model)。
隐式多对多(推荐,简单场景)
model Post {
id Int @id @default(autoincrement())
title String
tags Tag[]
}
model Tag {
id Int @id @default(autoincrement())
name String @unique
posts Post[]
}
你不需要自己建中间表。Prisma 会自动在数据库里创建一个隐式的中间表 _PostToTag,包含两列:A 和 B,分别指向 Post 和 Tag 的主键。
Client 查询用法:
// 创建 Post 并关联已有的 Tag
await prisma.post.create({
data: {
title: 'Prisma 上手教程',
tags: { connect: { name: 'prisma' } },
},
})
// 带 tags 查询
const post = await prisma.post.findUnique({
where: { id: 1 },
include: { tags: true },
})
显式多对多(中间表有额外字段时)
如果中间表本身需要额外信息(比如 assignedAt 标签分配时间),就手动定义中间表:
model Post {
id Int @id @default(autoincrement())
title String
tags PostTag[]
}
model Tag {
id Int @id @default(autoincrement())
name String @unique
posts PostTag[]
}
model PostTag {
postId Int
tagId Int
assignedAt DateTime @default(now())
post Post @relation(fields: [postId], references: [id])
tag Tag @relation(fields: [tagId], references: [id])
@@id([postId, tagId])
}
中间表 PostTag 有独立的 model 定义,可以像普通 model 一样查询和写入。用 @@id([postId, tagId]) 把两个外键列组合成复合主键。
关系行为:onDelete
定义关系时可以指定删除行为:
author User? @relation(
fields: [authorId],
references: [id],
onDelete: Cascade // 删除用户时级联删除他的文章
)
| 值 | 行为 |
|---|---|
Cascade | 删除主表记录时,从表关联记录也一起删 |
Restrict / NoAction | 有外键关联时禁止删除主表记录 |
SetNull | 删除主表记录时,外键列设为 NULL(从表可空才适用) |
SetDefault | 删除主表记录时,外键列设为默认值(PostgreSQL 支持) |