跳到主要內容

Prisma ORM MongoDB 資料庫聯結器

本指南討論了使用 Prisma ORM 和 MongoDB 的概念,解釋了 MongoDB 與其他資料庫提供商的異同,並引導您完成配置應用程式以使用 Prisma ORM 與 MongoDB 整合的過程。

資訊

要將 Prisma ORM 連線到 MongoDB,請參閱我們的入門文件

什麼是 MongoDB?

MongoDB 是一個 NoSQL 資料庫,它以 BSON 格式儲存資料,這是一種類似於 JSON 的文件格式,設計用於以鍵值對儲存資料。它通常用於 JavaScript 應用程式開發,因為文件模型可以輕鬆對映到應用程式程式碼中的物件,並且內建了對高可用性和水平擴充套件的支援。

MongoDB 將資料儲存在集合中,這些集合不需要像關係型資料庫中的表那樣預先定義模式。每個集合的結構也可以隨著時間而改變。這種靈活性可以實現資料模型的快速迭代,但這意味著在使用 Prisma ORM 處理 MongoDB 資料庫時,存在許多差異。

與其他資料庫提供商的共通之處

使用 Prisma ORM 與 MongoDB 的某些方面與使用 Prisma ORM 與關係型資料庫相同。您仍然可以:

需要考慮的差異

MongoDB 的基於文件的結構和靈活的模式意味著使用 Prisma ORM 與 MongoDB 不同於使用關係型資料庫。以下是您需要注意的一些差異領域:

  • 定義 ID:MongoDB 文件有一個 _id 欄位(通常包含一個 ObjectID)。Prisma ORM 不支援以 _ 開頭的欄位,因此需要使用 @map 屬性將其對映到 Prisma ORM 欄位。有關更多資訊,請參閱在 MongoDB 中定義 ID

  • 遷移現有資料以匹配您的 Prisma 模式:在關係型資料庫中,所有資料都必須與您的模式匹配。如果您在遷移時更改模式中特定欄位的型別,則所有資料也必須更新以匹配。相反,MongoDB 不強制執行任何特定模式,因此在遷移時需要小心。有關更多資訊,請參閱如何將舊資料遷移到新模式

  • 內省和 Prisma ORM 關係:當您內省現有的 MongoDB 資料庫時,您將獲得一個沒有關係的模式,並且需要手動新增缺失的關係。有關更多資訊,請參閱內省後如何新增缺失的關係

  • 過濾 null 和缺失欄位:MongoDB 在將欄位設定為 null 和根本不設定之間進行了區分,這在關係型資料庫中不存在。Prisma ORM 目前不表達這種區分,這意味著在過濾 null 和缺失欄位時需要小心。有關更多資訊,請參閱如何過濾 null 和缺失欄位

  • 啟用複製:Prisma ORM 在內部使用 MongoDB 事務,以避免巢狀查詢中的部分寫入。使用事務時,MongoDB 要求啟用資料集的複製。為此,您需要配置一個 副本集 —— 這是一組維護相同資料集的 MongoDB 程序。請注意,透過建立一個只有一個節點的副本集,仍然可以使用單個數據庫。如果您使用 MongoDB 的 Atlas 託管服務,副本集已為您配置,但如果您在本地執行 MongoDB,則需要自己設定副本集。有關更多資訊,請參閱 MongoDB 的部署副本集指南

大型集合的效能考慮

問題

透過 Prisma 處理大型 MongoDB 集合時,某些操作可能會變得緩慢且佔用資源。特別是,需要掃描整個集合的操作(例如 count())可能會達到查詢執行時間限制,並隨著資料集的增長顯著影響效能。

解決方案

要解決大型 MongoDB 集合的效能問題,請考慮以下方法:

  1. 對於大型集合,請考慮使用 MongoDB 的 estimatedDocumentCount() 而不是 count()。此方法速度快得多,因為它使用有關集合的元資料。您可以使用 Prisma 的 runCommandRaw 方法執行此命令。

  2. 對於頻繁訪問的計數,請考慮實現一個計數器快取。這涉及維護一個單獨的文件,其中包含預先計算的計數,您可以在新增或刪除文件時更新這些計數。

如何使用 Prisma ORM 與 MongoDB

本節提供瞭如何執行需要特定於 MongoDB 的步驟的任務的說明。

如何遷移現有資料以匹配您的 Prisma 模式

隨著時間的推移遷移資料庫是開發週期的重要組成部分。在開發過程中,您需要更新您的 Prisma 模式(例如,新增新欄位),然後更新開發環境資料庫中的資料,並最終將更新後的模式和新資料推送到生產資料庫。

資訊

使用 MongoDB 時,請注意您的模式與資料庫之間的“耦合”特意設計得比 SQL 資料庫不那麼嚴格;MongoDB 不會強制執行模式,因此您必須驗證資料完整性。

這些更新模式和資料庫的迭代任務可能導致模式與資料庫中的實際資料之間出現不一致。讓我們來看一個可能發生這種情況的場景,然後研究幾種供您和您的團隊考慮的策略來處理這些不一致。

場景:您需要為使用者新增電話號碼和電子郵件。您當前的 schema.prisma 檔案中包含以下 User 模型:

prisma/schema.prisma
model User {
id String @id @default(auto()) @map("_id") @db.ObjectId
email String
}

您可以採用多種策略來遷移此模式:

  • “按需”更新:採用此策略,您和您的團隊已同意可以根據需要對模式進行更新。但是,為了避免由於資料和模式之間的不一致而導致的遷移失敗,團隊中達成一致,任何新增欄位都明確定義為可選欄位。

    在上述場景中,您可以在 Prisma 模式的 User 模型中新增一個可選的 phoneNumber 欄位:

    prisma/schema.prisma
    model User {
    id String @id @default(auto()) @map("_id") @db.ObjectId
    email String
    phoneNumber String?
    }

    然後使用 npx prisma generate 命令重新生成您的 Prisma Client。

    接下來,更新您的應用程式以反映新欄位,並重新部署您的應用程式。

    由於 phoneNumber 欄位是可選的,您仍然可以查詢尚未定義電話號碼的舊使用者。資料庫中的記錄將隨著應用程式使用者開始在新欄位中輸入其電話號碼而“按需”更新。

    另一種選擇是在必填欄位上新增預設值,例如:

    prisma/schema.prisma
    model User {
    id String @id @default(auto()) @map("_id") @db.ObjectId
    email String
    phoneNumber String @default("000-000-0000")
    }

    然後,當您遇到缺失的 phoneNumber 時,該值將被強制轉換為 000-000-0000

  • “無破壞性更改”更新:此策略基於第一個策略,團隊之間進一步達成共識,即您不重新命名或刪除欄位,只新增新欄位,並且始終將新欄位定義為可選。此策略可以透過在 CI/CD 過程中新增檢查來加強,以驗證模式沒有向後不相容的更改。

  • “一次性”更新:此策略類似於關係型資料庫中的傳統遷移,所有資料都會更新以反映新模式。在上述場景中,您將建立一個指令碼,為資料庫中所有現有使用者的電話號碼欄位新增一個值。然後,您可以在應用程式中將該欄位設定為必填欄位,因為模式和資料是一致的。

內省後如何新增缺失的關係

在內省現有 MongoDB 資料庫後,您需要手動新增模型之間的關係。MongoDB 沒有透過外部索引鍵定義關係的概念,就像在關係型資料庫中一樣。但是,如果您的 MongoDB 集合中有一個“類似外部索引鍵”的欄位與另一個集合的 ID 欄位匹配,Prisma ORM 將允許您模擬集合之間的關係。

例如,假設一個 MongoDB 資料庫包含兩個集合:UserPost。這些集合中的資料具有以下格式,其中 userId 欄位將使用者與帖子關聯:

User 集合

  • _id 欄位,型別為 objectId
  • email 欄位,型別為 string

Post 集合

  • _id 欄位,型別為 objectId
  • title 欄位,型別為 string
  • userId 欄位,型別為 objectID

使用 db pull 內省後,這將被拉入 Prisma Schema,如下所示:

prisma/schema.prisma
model Post {
id String @id @default(auto()) @map("_id") @db.ObjectId
title String
userId String @db.ObjectId
}

model User {
id String @id @default(auto()) @map("_id") @db.ObjectId
email String
}

這缺少 UserPost 模型之間的關係。要解決此問題,請手動向 Post 模型新增一個 user 欄位,並使用 @relation 屬性,將 userId 作為 fields 值,將其連結到 User 模型,並向 User 模型新增一個 posts 欄位作為反向關係:

prisma/schema.prisma
model Post {
id String @id @default(auto()) @map("_id") @db.ObjectId
title String
userId String @db.ObjectId
user User @relation(fields: [userId], references: [id])
}

model User {
id String @id @default(auto()) @map("_id") @db.ObjectId
email String
posts Post[]
}

有關如何在 Prisma ORM 中使用關係的更多資訊,請參閱我們的文件

如何過濾 null 和缺失欄位

為了理解 MongoDB 如何區分 null 和缺失欄位,考慮一個帶有可選 name 欄位的 User 模型的示例:

model User {
id String @id @default(auto()) @map("_id") @db.ObjectId
email String
name String?
}

首先,嘗試建立一個 name 欄位明確設定為 null 的記錄。Prisma ORM 將按預期返回 name: null

const createNull = await prisma.user.create({
data: {
email: 'user1@prisma.io',
name: null,
},
})
console.log(createNull)
顯示CLI結果
{
id: '6242c4ae032bc76da250b207',
email: 'user1@prisma.io',
name: null
}

如果您直接檢查您的 MongoDB 資料庫,您也會看到一個 name 設定為 null 的新記錄:

{
"_id": "6242c4af032bc76da250b207",
"email": "user1@prisma.io",
"name": null
}

接下來,嘗試建立一個沒有明確設定 name 欄位的記錄:

const createMissing = await prisma.user.create({
data: {
email: 'user2@prisma.io',
},
})
console.log(createMissing)
顯示CLI結果
{
id: '6242c4ae032bc76da250b208',
email: 'user2@prisma.io',
name: null
}

Prisma ORM 仍然返回 name: null,但如果您直接檢視資料庫,您會看到該記錄根本沒有定義 name 欄位:

{
"_id": "6242c4af032bc76da250b208",
"email": "user2@prisma.io"
}

Prisma ORM 在兩種情況下都返回相同的結果,因為我們目前無法在 MongoDB 中指定底層資料庫中為 null 的欄位與根本未定義的欄位之間的這種差異——有關更多資訊,請參閱此 Github 問題

這意味著您目前在過濾 null 和缺失欄位時必須小心。過濾 name: null 的記錄只會返回第一條記錄,其中 name 明確設定為 null

const findNulls = await prisma.user.findMany({
where: {
name: null,
},
})
console.log(findNulls)
顯示CLI結果
[
{
id: '6242c4ae032bc76da250b207',
email: 'user1@prisma.io',
name: null
}
]

這是因為 name: null 正在檢查相等性,而不存在的欄位不等於 null

要同時包含缺失欄位,請使用 isSet 過濾器 顯式搜尋為 null 或未設定的欄位。這將返回兩條記錄:

const findNullOrMissing = await prisma.user.findMany({
where: {
OR: [
{
name: null,
},
{
name: {
isSet: false,
},
},
],
},
})
console.log(findNullOrMissing)
顯示CLI結果
[
{
id: '6242c4ae032bc76da250b207',
email: 'user1@prisma.io',
name: null
},
{
id: '6242c4ae032bc76da250b208',
email: 'user2@prisma.io',
name: null
}
]

更多關於使用 Prisma ORM 和 MongoDB 的資訊

開始使用 Prisma ORM 和 MongoDB 的最快方法是查閱我們的入門文件:

這些教程將引導您完成連線到 MongoDB、推送模式更改以及使用 Prisma Client 的過程。

更多參考資訊可在MongoDB 聯結器文件中找到。

有關如何設定和管理 MongoDB 資料庫的更多資訊,請參閱 Prisma 資料指南

示例

要連線到 MongoDB 伺服器,請在您的 Prisma Schema 中配置 datasource 塊:

schema.prisma
datasource db {
provider = "mongodb"
url = env("DATABASE_URL")
}

傳遞給 datasource 塊的欄位是:

  • provider:指定 mongodb 資料來源聯結器。
  • url:指定 MongoDB 伺服器的連線 URL。在這種情況下,使用環境變數 提供連線 URL。
警告

MongoDB 資料庫聯結器使用事務來支援巢狀寫入。事務需要副本集部署。部署副本集的最簡單方法是使用 Atlas。它提供免費入門。

連線詳情

連線 URL

MongoDB 連線 URL 可以根據您託管資料庫的方式以不同的方式配置。標準配置由以下元件組成:

Structure of the MongoDB connection URL

基本 URL 和路徑

連線 URL 的基本 URL 和路徑部分由您的身份驗證憑據、主機(以及可選的埠號)和資料庫組成。

mongodb://USERNAME:PASSWORD@HOST/DATABASE

以下元件構成了資料庫的基本 URL

名稱佔位符描述
使用者USERNAME您的資料庫使用者名稱,例如 janedoe
密碼PASSWORD您的資料庫使用者的密碼
主機HOST執行 mongod 例項的主機。如果您執行的是分片叢集,則這將是 mongos 例項。這可以是主機名、IP 地址或 UNIX 域套接字。
PORT您的資料庫伺服器執行的埠,例如 1234。如果未提供,則使用預設值 27017
資料庫DATABASE要使用的資料庫名稱。如果未指定,但設定了 authSource 選項,則使用 authSource 資料庫名稱。如果連線字串中的資料庫和 authSource 選項均未指定,則預設為 admin

引數

連線 URL 還可以接受引數。以下示例設定了三個引數:

  • ssl 連線
  • connectTimeoutMS
  • 以及 maxPoolSize
mongodb://USERNAME:PASSWORD@HOST/DATABASE?ssl=true&connectTimeoutMS=5000&maxPoolSize=50

有關連線字串引數的完整列表,請參閱 MongoDB 連線字串文件。沒有 Prisma ORM 特定的引數。

使用 ObjectId

MongoDB 文件的 _id 欄位包含 ObjectId 是一種常見做法:

{
"_id": { "$oid": "60d599cb001ef98000f2cad2" },
"createdAt": { "$date": { "$numberLong": "1624611275577" } },
"email": "ella@prisma.io",
"name": "Ella",
"role": "ADMIN"
}

任何對映到底層資料庫中 ObjectId 的欄位(最常見的是 ID 和關係標量欄位):

  • 必須是 StringBytes 型別
  • 必須包含 @db.ObjectId 屬性
  • 可以選擇使用 @default(auto()) 在文件建立時自動生成有效的 ObjectId

這是一個使用 String 的例子:

model User {
id String @id @default(auto()) @map("_id") @db.ObjectId
// Other fields
}

這是另一個使用 Bytes 的例子:

model User {
id Bytes @id @default(auto()) @map("_id") @db.ObjectId
// Other fields
}

另請參閱:在 MongoDB 中定義 ID 欄位

生成 ObjectId

要在您的應用程式中生成有效的 ObjectId(用於測試目的或手動設定 ID 欄位值),請使用 bson 包。

npm install --save bson
import { ObjectId } from 'bson'

const id = new ObjectId()

與關係型資料庫聯結器的差異

本節介紹了 MongoDB 聯結器與 Prisma ORM 關係型資料庫聯結器的不同之處。

不支援 Prisma Migrate

目前,沒有計劃新增對 Prisma Migrate 的支援,因為 MongoDB 專案不依賴於需要使用額外工具管理更改的內部模式。@unique 索引的管理透過 db push 實現。

不支援 @@idautoincrement()

不支援 @@id 屬性(多個欄位的 ID),因為 MongoDB 中的主鍵始終位於模型的 _id 欄位上。

不支援 autoincrement() 函式(建立自增 @id 值),因為 autoincrement() 不適用於 MongoDB 中 _id 欄位的 ObjectID 型別。

迴圈引用和引用操作

如果您的模型中存在迴圈引用(無論是自引用還是模型之間關係的迴圈),並且您使用引用操作,則必須將引用操作設定為 NoAction 以防止無限迴圈。

有關更多詳細資訊,請參閱引用操作的特殊規則

副本集配置

MongoDB 僅允許在副本集上啟動事務。Prisma ORM 內部使用事務來避免巢狀查詢中的部分寫入。這意味著我們繼承了需要配置副本集的要求。

當您嘗試在未配置副本集的部署上使用 Prisma ORM 的 MongoDB 聯結器時,Prisma ORM 會顯示訊息 Error: Transactions are not supported by this deployment。錯誤訊息的完整文字如下:

PrismaClientUnknownRequestError2 [PrismaClientUnknownRequestError]:
Invalid `prisma.post.create()` invocation in
/index.ts:9:21

6 await prisma.$connect()
7
8 // Create the first post
→ 9 await prisma.post.create(
Error in connector: Database error. error code: unknown, error message: Transactions are not supported by this deployment
at cb (/node_modules/@prisma/client/runtime/index.js:34804:17)
at processTicksAndRejections (internal/process/task_queues.js:97:5) {
clientVersion: '3.xx.0'
}

要解決此問題,我們建議您將部署更改為已配置副本集的部署。

一個簡單的方法是使用 MongoDB Atlas 來啟動一個免費例項,該例項開箱即支援副本集。

還有一種選項可以使用此指南在本地執行副本集:https://www.mongodb.com/docs/manual/tutorial/convert-standalone-to-replica-set

MongoDB 與 Prisma 模式之間的型別對映

MongoDB 聯結器將 Prisma ORM 資料模型中的標量型別對映到 MongoDB 的原生列型別,如下所示:

或者,請參閱Prisma 模式參考,瞭解按 Prisma 型別組織的型別對映。

從 Prisma ORM 到 MongoDB 的原生型別對映

Prisma ORMMongoDB
Stringstring
Booleanbool
Intint
BigIntlong
Floatdouble
Decimal目前不支援
DateTimetimestamp
BytesbinData
Json

目前不支援的 MongoDB 型別

  • Decimal128
  • Undefined
  • DBPointer
  • Null
  • Symbol
  • MinKey
  • MaxKey
  • Object
  • Javascript
  • JavascriptWithScope
  • Regex

內省時從 MongoDB 到 Prisma ORM 型別的對映

在內省 MongoDB 資料庫時,Prisma ORM 使用相關的標量型別。某些特殊型別還會獲得額外的原生型別註釋:

MongoDB (型別 | 別名)Prisma ORM支援原生資料庫型別屬性備註
objectIdString✔️@db.ObjectId

內省尚不支援的原生資料庫型別新增為 Unsupported 欄位

schema.prisma
model Example {
id String @id @default(auto()) @map("_id") @db.ObjectId
name String
regex Unsupported("RegularExpression")
}
© . This site is unofficial and not affiliated with Prisma Data, Inc.