seansie's blog

深入理解 Prisma Model:型別修飾、屬性設定與關聯設計

在 Prisma Schema 中,資料模型(model)由純量型別(Scalar Types)型別修飾符(Type Modifiers)、欄位與模型屬性(Attributes)以及基數關聯(Cardinality / Relations)所組成。


1. 資料型別(Data Types)與修飾符

核心純量型別(Scalar Types)

Prisma 提供跨資料庫通用的純量型別,並可搭配 @db.* 指定底層資料庫的精確型別:

Prisma 型別 TypeScript 對應 常見資料庫對應 (PostgreSQL / MySQL) 說明
String string VARCHAR, TEXT 字串。可搭配 @db.VarChar(255)@db.Text
Boolean boolean BOOLEAN, TINYINT(1) 布林值
Int number INTEGER, INT 32 位元有號整數
BigInt bigint BIGINT 64 位元整數(TS 中以原生 BigInt 呈現)
Float number DOUBLE PRECISION, FLOAT 浮點數(IEEE 754)
Decimal Prisma.Decimal DECIMAL(p, s), NUMERIC 高精度小數(適合金額計算,避免浮點誤差)
DateTime Date TIMESTAMP, TIMESTAMPTZ ISO-8601 時間戳記
Json Prisma.JsonValue JSON, JSONB 結構化 JSON 物件
Bytes Buffer BYTEA, BLOB 二進位二進制資料

型別修飾符(Modifiers)

  • **選填(Optional)?**:允許欄位為 NULL(如 bio String?)。
  • **陣列(List)[]**:純量陣列(如 tags String[],僅 PostgreSQL/CockroachDB 原生支援;在 MySQL/SQLite 通常需透過關聯表實作)。

列舉(Enums)

自訂常數集合(需資料庫支援或由 Prisma 模擬):

enum Role {
  USER
  ADMIN
  MODERATOR
}

model User {
  id   Int  @id @default(autoincrement())
  role Role @default(USER)
}

2. 屬性(Attributes)與函式(Functions)

屬性分為欄位級屬性(以 @ 開頭)模型級屬性(以 @@ 開頭)

欄位級屬性(Field Attributes)

  • @id:將欄位設為主鍵(Primary Key)。

  • @default(...):設定預設值,支援內建函式:

  • autoincrement():自增整數。

  • uuid() / cuid() / nanoid():產生唯一識別碼字串。

  • now():當前時間戳記。

  • dbgenerated(...):調用資料庫原生函式(如 gen_random_uuid())。

  • @unique:設定唯一約束(Unique Constraint)。

  • @updatedAt:記錄更新時自動更新為當前時間。

  • @map("db_column_name"):映射到資料庫中實際的欄位名稱(蛇形命名 vs 駝峰命名)。

  • @ignore:Prisma Client 忽略該欄位(不產生型別)。

模型級屬性(Model Attributes)

  • @@id([field1, field2]):設定複合主鍵(Composite Primary Key)。
  • @@unique([field1, field2]):設定複合唯一約束。
  • @@index([field1, field2]):建立資料庫索引(支援指定索引類型,如 @@index([name], type: Gin))。
  • @@map("db_table_name"):映射到資料庫中實際的資料表名稱。
  • @@ignore:Prisma Client 忽略整張資料表。

3. 關聯基數(Cardinality & Relations)

關聯基數描述兩個模型之間的對應數量關係。Prisma 透過 @relation 屬性定義外鍵(Foreign Key)與參照。

1. 一對一(1-1 / One-to-One)

一筆資料恰好對應另一筆資料(例如:UserProfile)。

  • 規則:外鍵(userId)必須加上 @unique
model User {
  id      Int      @id @default(autoincrement())
  email   String   @unique
  profile Profile? // 反向關聯(虛擬欄位,不存於資料庫 User 表中)
}

model Profile {
  id     Int    @id @default(autoincrement())
  bio    String
  userId Int    @unique // 核心:唯一約束確保 1-1
  user   User   @relation(fields: [userId], references: [id], onDelete: Cascade)
}

2. 一對多(1-N / One-to-Many)

一筆資料可對應多筆資料(例如:UserPost)。

  • 規則:「多」的一方(Post)持有外鍵欄位(authorId)。
model User {
  id    Int    @id @default(autoincrement())
  posts Post[] // 一個使用者擁有多篇文章
}

model Post {
  id       Int    @id @default(autoincrement())
  title    String
  authorId Int    // 外鍵欄位
  author   User   @relation(fields: [authorId], references: [id], onDelete: Cascade)

  @@index([authorId])
}

3. 多對多(M-N / Many-to-Many)

隱式多對多(Implicit M-N) 雙方皆宣告為陣列(Tag[]Post[]),Prisma 會自動在底層建立並管理中介表(如 _PostToTag),無需手動定義 Model。

model Post {
  id   Int   @id @default(autoincrement())
  tags Tag[] // 隱式多對多
}

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

顯式多對多(Explicit M-N) 當中介表需要儲存額外屬性(如建立時間、角色權限、排序)時,必須手動定義中介 Model。

model Post {
  id         Int               @id @default(autoincrement())
  title      String
  categories PostOnCategory[]
}

model Category {
  id    Int              @id @default(autoincrement())
  name  String
  posts PostOnCategory[]
}

// 手動定義中介表
model PostOnCategory {
  postId     Int
  categoryId Int
  post       Post     @relation(fields: [postId], references: [id], onDelete: Cascade)
  category   Category @relation(fields: [categoryId], references: [id], onDelete: Cascade)
  assignedAt DateTime @default(now()) // 額外欄位
  assignedBy String?

  @@id([postId, categoryId]) // 複合主鍵
}

4. 參照動作(Referential Actions)

@relation 屬性中,可透過 onDeleteonUpdate 定義父記錄變更時子記錄的行為:

動作(Action) 行為說明
Cascade 父記錄刪除/更新時,自動刪除/更新關聯的子記錄。
SetNull 父記錄刪除/更新時,將子記錄的外鍵設為 NULL(外鍵欄位必須為 Optional)。
Restrict 若存在子記錄,阻止父記錄被刪除/更新。
NoAction 資料庫原生行為(類似 Restrict,但在交易結束時才檢查約束)。
SetDefault 父記錄刪除/更新時,將子記錄外鍵設為預設值。