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 與關係型資料庫相同。您仍然可以:
- 使用 Prisma Schema Language 建模您的資料庫
- 使用
mongodb資料庫聯結器 連線到您的資料庫 - 如果您已有 MongoDB 資料庫,可以使用 內省 進行現有專案
- 使用
db push將您的模式更改推送到資料庫 - 在您的應用程式中使用 Prisma Client,根據您的 Prisma Schema 以型別安全的方式查詢您的資料庫
需要考慮的差異
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 集合的效能問題,請考慮以下方法:
-
對於大型集合,請考慮使用 MongoDB 的
estimatedDocumentCount()而不是count()。此方法速度快得多,因為它使用有關集合的元資料。您可以使用 Prisma 的runCommandRaw方法執行此命令。 -
對於頻繁訪問的計數,請考慮實現一個計數器快取。這涉及維護一個單獨的文件,其中包含預先計算的計數,您可以在新增或刪除文件時更新這些計數。
如何使用 Prisma ORM 與 MongoDB
本節提供瞭如何執行需要特定於 MongoDB 的步驟的任務的說明。
如何遷移現有資料以匹配您的 Prisma 模式
隨著時間的推移遷移資料庫是開發週期的重要組成部分。在開發過程中,您需要更新您的 Prisma 模式(例如,新增新欄位),然後更新開發環境資料庫中的資料,並最終將更新後的模式和新資料推送到生產資料庫。
使用 MongoDB 時,請注意您的模式與資料庫之間的“耦合”特意設計得比 SQL 資料庫不那麼嚴格;MongoDB 不會強制執行模式,因此您必須驗證資料完整性。
這些更新模式和資料庫的迭代任務可能導致模式與資料庫中的實際資料之間出現不一致。讓我們來看一個可能發生這種情況的場景,然後研究幾種供您和您的團隊考慮的策略來處理這些不一致。
場景:您需要為使用者新增電話號碼和電子郵件。您當前的 schema.prisma 檔案中包含以下 User 模型:
model User {
id String @id @default(auto()) @map("_id") @db.ObjectId
email String
}
您可以採用多種策略來遷移此模式:
-
“按需”更新:採用此策略,您和您的團隊已同意可以根據需要對模式進行更新。但是,為了避免由於資料和模式之間的不一致而導致的遷移失敗,團隊中達成一致,任何新增欄位都明確定義為可選欄位。
在上述場景中,您可以在 Prisma 模式的
User模型中新增一個可選的phoneNumber欄位:prisma/schema.prismamodel User {
id String @id @default(auto()) @map("_id") @db.ObjectId
email String
phoneNumber String?
}然後使用
npx prisma generate命令重新生成您的 Prisma Client。接下來,更新您的應用程式以反映新欄位,並重新部署您的應用程式。
由於
phoneNumber欄位是可選的,您仍然可以查詢尚未定義電話號碼的舊使用者。資料庫中的記錄將隨著應用程式使用者開始在新欄位中輸入其電話號碼而“按需”更新。另一種選擇是在必填欄位上新增預設值,例如:
prisma/schema.prismamodel 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 資料庫包含兩個集合:User 和 Post。這些集合中的資料具有以下格式,其中 userId 欄位將使用者與帖子關聯:
User 集合
_id欄位,型別為objectIdemail欄位,型別為string
Post 集合
_id欄位,型別為objectIdtitle欄位,型別為stringuserId欄位,型別為objectID
使用 db pull 內省後,這將被拉入 Prisma Schema,如下所示:
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
}
這缺少 User 和 Post 模型之間的關係。要解決此問題,請手動向 Post 模型新增一個 user 欄位,並使用 @relation 屬性,將 userId 作為 fields 值,將其連結到 User 模型,並向 User 模型新增一個 posts 欄位作為反向關係:
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)
{
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)
{
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)
[
{
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)
[
{
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 塊:
datasource db {
provider = "mongodb"
url = env("DATABASE_URL")
}
傳遞給 datasource 塊的欄位是:
連線詳情
連線 URL
MongoDB 連線 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 和關係標量欄位):
- 必須是
String或Bytes型別 - 必須包含
@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 實現。
不支援 @@id 和 autoincrement()
不支援 @@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 ORM | MongoDB |
|---|---|
String | string |
Boolean | bool |
Int | int |
BigInt | long |
Float | double |
Decimal | 目前不支援 |
DateTime | timestamp |
Bytes | binData |
Json |
目前不支援的 MongoDB 型別
Decimal128UndefinedDBPointerNullSymbolMinKeyMaxKeyObjectJavascriptJavascriptWithScopeRegex
內省時從 MongoDB 到 Prisma ORM 型別的對映
在內省 MongoDB 資料庫時,Prisma ORM 使用相關的標量型別。某些特殊型別還會獲得額外的原生型別註釋:
| MongoDB (型別 | 別名) | Prisma ORM | 支援 | 原生資料庫型別屬性 | 備註 |
|---|---|---|---|---|
objectId | String | ✔️ | @db.ObjectId |
內省將尚不支援的原生資料庫型別新增為 Unsupported 欄位
model Example {
id String @id @default(auto()) @map("_id") @db.ObjectId
name String
regex Unsupported("RegularExpression")
}