跳至主要内容

Prisma ORM MongoDB 数据库连接器

本指南讨论了使用 Prisma ORM 和 MongoDB 背后的概念,解释了 MongoDB 与其他数据库提供程序之间的共性和差异,并引导您完成配置应用程序以使用 Prisma ORM 与 MongoDB 集成的过程。

Prisma ORM v7 的 MongoDB 支持

Prisma ORM v7 的 MongoDB 支持即将推出。在此期间,使用 MongoDB 时请使用 Prisma ORM v6.19(最新的 v6 版本)。

有关使用 Prisma ORM v6.19 和 MongoDB 的入门指南,请参阅

什么是 MongoDB?

MongoDB 是一个 NoSQL 数据库,它以 BSON 格式存储数据,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 将允许您模拟集合之间的关联。

例如,以一个包含两个集合 UserPost 的 MongoDB 数据库为例。这些集合中的数据具有以下格式,其中 userId 字段将用户链接到帖子

User 集合

  • _id 字段,类型为 objectId
  • email 字段,类型为 string

Post 集合

  • _id 字段,类型为 objectId
  • title 字段,类型为 string
  • userId 字段,类型为 objectID

使用 db pull 内省时,这将导入到 Prisma 模式中,如下所示

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
}
]

更多关于使用 MongoDB 与 Prisma ORM 的信息

开始使用 MongoDB 和 Prisma ORM 的最快方法是查阅我们的入门文档

这些教程将引导您完成连接到 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

Base URL 和路径

连接 URL 的基本 URL 和路径部分由您的身份验证凭据、主机(以及可选的端口号)和数据库组成。

mongodb://USERNAME:PASSWORD@HOST/DATABASE

以下组件构成数据库的基本 URL

名称占位符描述
User用户名数据库用户名,例如 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://mongodb.ac.cn/docs/manual/tutorial/convert-standalone-to-replica-set

MongoDB 与 Prisma 模式之间的类型映射

MongoDB 连接器将 Prisma ORM 数据模型中的标量类型映射到 MongoDB 的原生列类型,如下所示

或者,请参阅 Prisma 模式参考,了解按 Prisma 类型组织的类型映射。

从 Prisma ORM 到 MongoDB 的原生类型映射

Prisma ORMMongoDB
Stringstring
Boolean布尔值
Intint
BigInt长整型
Float双精度浮点型
Decimal目前不支持
DateTime时间戳
Bytes二进制数据
Json

目前不支持的 MongoDB 类型

  • Decimal128
  • Undefined
  • DBPointer
  • Null
  • Symbol
  • MinKey
  • MaxKey
  • 对象
  • 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.