一、简介

1.1) 数据库中的关系
关系是
Prisma模式中两个模型之间的链接。
1.2) Primsa 中的关系
在 Prisma 中通过关系
字段和外键以及@relation关联不同的模型。
1.3) 关系类型
1.4)显示和隐式关系
1.5) 关系字段
schema
model User {
id Int @id @default(autoincrement())
role Role @default(USER)
posts Post[]
}
model Post {
id Int @id @default(autoincrement())
author User @relation(fields: [authorId], references: [id])
authorId Int // relation scalar field (used in the `@relation` attribute above)
}
有 @relation 表示为关系字段。其中 User 模型的关系字段是 posts, Post 模型的关系字段是 user。
1.6)@relation 关系函数
@relation 类似一个函数可以传递的参数:
- name 标记名称
- fields 当前模型的字段列表(一般使用 id)
- references 关联模型的字段列表
- map 自定义名字
- onUpdate 定义更新关联 action
- onDelete 定义删除 action
1.7) 使用 name 消除歧义
- name 是第一个参数。
schame
model User {
id Int @id @default(autoincrement())
writtenPosts Post[] @relation("WrittenPosts")
pinnedPost Post? @relation("PinnedPost")
}
model Post {
id Int @id @default(autoincrement())
author User @relation("WrittenPosts", fields: [authorId], references: [id])
authorId Int
pinnedBy User? @relation("PinnedPost", fields: [pinnedById], references: [id])
pinnedById Int? @unique
}
如果不适用 name 当关系比较复杂时候,很难一眼看出关联关系。在 @relation 中使用 name,可以消除歧义。
二、一对一关系
用户和用户详情是一个常见的一对一关系,以下会以这种User/Profile的模型。
2.1) 关系型
schema
model User {
id Int @id @default(autoincrement())
profile Profile?
}
model Profile {
id Int @id @default(autoincrement())
user User @relation(fields: [userId], references: [id])
userId Int @unique
}
用户中的 profile 与 Profile 中的 user 构成一对一的关联关系。
2.2) 非关系型 MongoDB
schema
model User {
id String @id @default(auto()) @map("_id") @db.ObjectId
profile Profile?
}
model Profile {
id String @id @default(auto()) @map("_id") @db.ObjectId
user User @relation(fields: [userId], references: [id])
userId String @unique @db.ObjectId
}
MongoDB 与关系型数据库使用类似,更具 MongoDB 的特点,类型,属性发生一些变化。例如 MongoDB 中的 id 用字符串笔记合理。
2.3) 可选性
在一对一关系中,没有 关系标量的关系一侧(表示数据库中外键的字段)必须是可选的:
profile Profile?
2.4) 关联位置选择
关联位置是可选的,将以上的例子交换关联位置, 在 Uesr 中进行关联:
schema
model User {
id Int @id @default(autoincrement())
profile Profile? @relation(fields: [profileId], references: [id])
profileId Int? @unique // relation scalar field (used in the `@relation` attribute above)
}
model Profile {
id Int @id @default(autoincrement())
user User?
}
三、一对多关系
用户和用户发表的内容(文章、博客Post)是一个常见的一对一关系,以下会以这种User/Post的模型。
多字段关系
与 一对一类似, 使用 @relation 标记和模型数组。
关系型
schema
model User {
id Int @id @default(autoincrement())
posts Post[]
}
model Post {
id Int @id @default(autoincrement())
author User @relation(fields: [authorId], references: [id])
authorId Int
}
User 模型中的 posts 字段与 Post 模型中的 author 字段配合 @relation 进行关联。注意 posts 类型是 Post[]列表形式。
非关系型 MongoDB
schema
model User {
id String @id @default(auto()) @map("_id") @db.ObjectId
posts Post[]
}
model Post {
id String @id @default(auto()) @map("_id") @db.ObjectId
author User @relation(fields: [authorId], references: [id])
authorId String @db.ObjectId
}
非关系型 MongoDB 的用法与关系型基本一致,类型上有所区别。
可选性
注意以上 Post 示例没有标记为可选的,但是在多对多的关系中,是可以标记为可选的。
schema
model User {
id Int @id @default(autoincrement())
posts Post[]
}
model Post {
id Int @id @default(autoincrement())
author User? @relation(fields: [authorId], references: [id])
authorId Int?
}
可选的意味着,在创建的时候可以不分配内容。
四、多对多关系
多对多关系: 关系一侧的零个或多个记录可以连接到另一侧的零个或多个记录的关系。
显式多对多关系
- 显示定义多对多一般需要三个模型(包含一个中间模型)
- 一种表示关系表的模型,例如底层数据库中的关系表(有时也称为JOIN 、链接 或数据透视表)。
例如:博客的 Post 与博客分类 Category, 通过 CategoriesOnPosts 中间模型进行关联。
schema
model Post {
id Int @id @default(autoincrement())
title String
categories CategoriesOnPosts[]
}
model Category {
id Int @id @default(autoincrement())
name String
posts CategoriesOnPosts[]
}
model CategoriesOnPosts {
post Post @relation(fields: [postId], references: [id])
postId Int // relation scalar field (used in the `@relation` attribute above)
category Category @relation(fields: [categoryId], references: [id])
categoryId Int // relation scalar field (used in the `@relation` attribute above)
assignedAt DateTime @default(now())
assignedBy String
@@id([postId, categoryId])
}
需要注意的是: Post/Category 模型需要与 CategoriesOnPosts 中间模型也进行关联。
隐式多对多关系
与 显示不同的是,隐式的没有中间(或者隐藏了)以下是简单的示例
schema
model Post {
id Int @id @default(autoincrement())
title String
categories Category[]
}
model Category {
id Int @id @default(autoincrement())
name String
posts Post[]
}
没有中间模型,之后 Post 与 Category 直接进行关联,并且没有 @relation 关联属性。表示多对多关系。
关系表
-
关系表(有时也称为JOIN、链接或数据透视表)连接两个或多个其他表,从而在它们之间创建关系。
-
创建关系表是 SQL 中常见的数据建模实践,用于表示不同实体之间的关系。
-
本质上,它意味着 "在数据库中将一个多对多关系建模为两个 一对多关系"。
-
建议使用隐式多对多关系,其中 Prisma 自动在底层数据库中生成关系表。
-
当需要在关系中存储附加数据(例如创建关系的日期)时,应使用显式多对多关系。
MongoDB
MongoDB 与关系型数据不同:
- 需要
@relation属性 - 带有强制的
fields和references参数 - 每侧引用对应的 ID
schema
model Post {
id String @id @default(auto()) @map("_id") @db.ObjectId
categoryIDs String[] @db.ObjectId
categories Category[] @relation(fields: [categoryIDs], references: [id])
}
model Category {
id String @id @default(auto()) @map("_id") @db.ObjectId
name String
postIDs String[] @db.ObjectId
posts Post[] @relation(fields: [postIDs], references: [id])
}
五、自我关系
在数据中有一类特殊的,类似于
树形结构数据需要处理。以下会以菜单和关注与被关注为例讲解
一对一的自我关系
schema
model MenuItem {
id Int @id @default(autoincrement())
label String
parent MenuItem? @relation("ChildToParent", fields: [parentId], references: [id])
parentId Int?
children MenuItem[] @relation("ChildToParent")
}
子路由 children 只能有一个 parent 父路由,构成了 自我关系的 一对一关系。
一对多的自关系
schema
model MenuItem {
id Int @id @default(autoincrement())
label String
parent MenuItem? @relation("ChildToParent", fields: [parentId], references: [id])
parentId Int?
children MenuItem[] @relation("ChildToParent")
}
parent 中包含了 children 可以是多个, 构成 自我关系 一对多的关系。
多对多自关系
schema
model User {
id Int @id @default(autoincrement())
name String?
followedBy User[] @relation("UserFollows")
following User[] @relation("UserFollows")
}
作为一个用户,可以被多个人关注,作为用户,也可以关注很多人,这是多对多关系。为了消除歧义,此时使用 @relation属性进行标记, 当然值得主要是这是 多对多关系中的隐式关系。
schema
model User {
id Int @id @default(autoincrement())
name String?
followedBy Follows[] @relation("following")
following Follows[] @relation("follower")
}
model Follows {
follower User @relation("follower", fields: [followerId], references: [id])
followerId Int
following User @relation("following", fields: [followingId], references: [id])
followingId Int
@@id([followerId, followingId])
}
六、关系中的 Action
onDelete
用于定义在父记录(包含外键的记录)被删除时,与该父记录相关联的子记录的行为。它控制了删除操作对关联子记录的影响
onUpdate
用于定义在父记录的主键值被更新时,与该父记录相关联的子记录的行为。它控制了更新操作对关联子记录的影响
Cascade
- delete 中:表示当父记录被删除时,相关联的子记录也会被删除。
- update 中:表示当父记录的主键值被更新时,相关联的子记录也会相应地更新外键字段。
Restrict
- delete 中:外键约束操作选项,表示当尝试删除新父记录时,如果有关联的子记录存在,将会限制(阻止)这些操作。
- update 中:外键约束操作选项,表示当尝试更新父记录时,如果有关联的子记录存在,将会限制(阻止)这些操作。
Restrict选项时,你必须先删除或更新子记录,然后才能删除或更新父记录。
NoAction
- delete 中:表示不执行任何操作,保留外键约束,不允许删除父记录,直到相关的子记录被删除或更新。
- update 中:表示不执行任何操作,保留外键约束,不允许更新父记录的主键值,直到相关的子记录被删除或更新。
SetNull
- delete 中:表示当父记录被删除时,相关联的子记录的外键字段将被设置为
NULL。 - update 中:表示当父记录的主键值被更新时,相关联的子记录的外键字段将被设置为
NULL。
SetDefault
- delete 中:外键约束操作选项,表示当尝试删除父记录时,将会将子记录的外键字段设置为其默认值。
- update 中:外键约束操作选项,表示当尝试更新父记录时,将会将子记录的外键字段设置为其默认值。
默认值是在创建表格时定义的,通常是一个合适的默认值或
NULL,具体取决于表格设计。
七、关系后迁移
如果你的schema 发生变化使用 db push 命令同步数据模型
sh
npx prisma db push
- 创建新的数据表
- 更新表
- 数据迁移
- 更新外键约束
八、关系模式
foreignKeys(默认)
foreignKeys 模式:
| 特点 | 描述 |
|---|---|
| 外键约束 | 依赖数据库层面的外键约束来维护数据关系的完整性和一致性。 |
| 外键字段创建 | 外键字段在数据库表格中创建,用于明确表示关系。 |
| 删除和更新行为 | 使用 onDelete 和 onUpdate 选项定义在删除或更新父记录时如何处理相关子记录。 |
| 适用场景 | 适用于需要强制执行数据库级别外键约束的情况,以确保数据完整性。 |
prisma
| 特点 | 描述 |
|---|---|
| 数据库无外键约束 | 不依赖数据库外键约束,关系通过 Prisma 数据模型定义,外键字段不一定在数据库中创建。 |
| Prisma Client 管理 | Prisma Client 负责在应用程序层面管理关系,包括创建、查询、更新和删除关联记录。 |
| 灵活性和控制 | 提供更多灵活性和控制,可以使用 include 和 select 等查询选项来检索和操作关联数据。 |
| 适用场景 | 适用于不依赖数据库外键约束,而是在应用程序层面管理关系的情况,提供更多灵活性和抽象性。 |
- 使用
schema
datasource db {
provider = "mysql"
url = env("DATABASE_URL")
relationMode = "prisma"
}
九、小结
本文主要讲解了 Primsa 中的数据模型之间的关系,从 一对一、一对多和多对多,到后面的 自我关系,到最后的 Action 和数据迁移模式讲解,全面的概括了 Prisma 中模型之间的关系。Prisma + typescript 在数据库操作方面,高效方便,非常值得学习。