跳至主要内容

MongoDB

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

信息

要将 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 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
}

您可以使用多种策略来迁移此架构

  • “按需”更新:使用此策略,您和您的团队已同意可以根据需要对架构进行更新。但是,为了避免由于数据和架构之间不一致而导致的迁移失败,团队中有一致意见,即添加的任何新字段都被明确定义为可选。

    在上面的场景中,您可以将可选的 phoneNumber 字段添加到 Prisma 架构中的 User 模型中

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

    然后使用 npx prisma generate 命令重新生成 Prisma 客户端。接下来,更新您的应用程序以反映新字段,然后重新部署您的应用程序。

    由于 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 模型之间的关系。要解决此问题,请使用 userId 作为 fields 值,手动向 Post 模型添加 user 字段,并使用 @relation 属性将其链接到 User 模型,并将 posts 字段添加到 User 模型作为反向关系

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: '[email protected]',
name: null,
},
})
console.log(createNull)
显示CLI结果
{
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)
显示CLI结果
{
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)
显示CLI结果
[
{
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)
显示CLI结果
[
{
id: '6242c4ae032bc76da250b207',
email: '[email protected]',
name: null
},
{
id: '6242c4ae032bc76da250b208',
email: '[email protected]',
name: null
}
]

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

使用 Prisma ORM 开始使用 MongoDB 的最快方法是参考我们的入门文档

这些教程将引导您完成连接到 MongoDB、推送架构更改以及使用 Prisma 客户端的过程。

更多参考信息可在 MongoDB 连接器文档 中找到。

有关如何设置和管理 MongoDB 数据库的更多信息,请参阅 Prisma 数据指南

示例

要连接到 MongoDB 服务器,请配置 datasource 块,并在 Prisma 架构

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

传递到 datasource 块的字段是

  • provider:指定 mongodb 数据源连接器。
  • url:指定 连接 URL,用于 MongoDB 服务器。在本例中,环境变量 用于提供连接 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": "[email protected]",
"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://docs.mongodb.com/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")
}