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
}
您可以使用多种策略来迁移此模式
-
“按需”更新:使用此策略,您和您的团队已同意可以根据需要对模式进行更新。 但是,为了避免由于数据和模式之间的不一致而导致迁移失败,团队内部达成协议,任何添加的新字段都显式定义为可选。
在上面的场景中,您可以将可选的
phoneNumber
字段添加到 Prisma 模式中的User
模型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 将允许您模拟集合之间的关系。
例如,采用一个包含两个集合 User
和 Post
的 MongoDB 数据库。 这些集合中的数据格式如下,其中 userId
字段将用户链接到帖子
User
集合
_id
字段,类型为objectId
email
字段,类型为string
Post
集合
_id
字段,类型为objectId
title
字段,类型为string
userId
,类型为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: '[email protected]',
name: null,
},
})
console.log(createNull)
{
id: '6242c4ae032bc76da250b207',
email: '[email protected]',
name: null
}
如果您直接检查 MongoDB 数据库,您还将看到一条新记录,其中 name
设置为 null
{
"_id": "6242c4af032bc76da250b207",
"email": "[email protected]",
"name": null
}
接下来,尝试创建一个记录,而不显式设置 name
字段
const createMissing = await prisma.user.create({
data: {
email: '[email protected]',
},
})
console.log(createMissing)
{
id: '6242c4ae032bc76da250b208',
email: '[email protected]',
name: null
}
Prisma ORM 仍然返回 name: null
,但如果您直接查看数据库,您将看到该记录根本没有定义 name
字段
{
"_id": "6242c4af032bc76da250b208",
"email": "[email protected]"
}
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: '[email protected]',
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: '[email protected]',
name: null
},
{
id: '6242c4ae032bc76da250b208',
email: '[email protected]',
name: null
}
]
有关将 MongoDB 与 Prisma ORM 一起使用的更多信息
开始将 MongoDB 与 Prisma ORM 一起使用的最快方法是参考我们的快速入门文档
这些教程将引导您完成连接到 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": "[email protected]",
"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://mongodb.ac.cn/docs/manual/tutorial/convert-standalone-to-replica-set
MongoDB 和 Prisma schema 之间的类型映射
MongoDB 连接器将 Prisma ORM 数据模型中的标量类型映射到 MongoDB 的本机列类型,如下所示
或者,请参阅Prisma schema 参考,了解按 Prisma 类型组织的类型映射。
从 Prisma ORM 到 MongoDB 的本机类型映射
Prisma ORM | MongoDB |
---|---|
String | string |
Boolean | bool |
Int | int |
BigInt | long |
Float | double |
Decimal | 当前不支持 |
DateTime | timestamp |
Bytes | binData |
Json |
当前不支持的 MongoDB 类型
Decimal128
Undefined
DBPointer
Null
Symbol
MinKey
MaxKey
Object
Javascript
JavascriptWithScope
Regex
从 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")
}