数据建模
什么是数据建模?
术语 数据建模 是指定义应用程序中对象的形状和结构的过程,这些对象通常被称为“应用程序模型”。在关系型数据库(如 PostgreSQL)中,它们存储在 表 中。在使用文档型数据库(如 MongoDB)时,它们存储在 集合 中。
根据你应用程序的领域,模型会有所不同。例如,如果你正在编写一个博客应用程序,你可能会有 博客、作者、文章 等模型。在编写拼车应用程序时,你可能会有 司机、汽车、路线 等模型。应用程序模型使你能够通过创建相应的数据结构在代码中表示这些不同的实体。
在数据建模时,你通常会问以下问题:
- 我应用程序中的主要实体/概念是什么?
- 它们之间如何关联?
- 它们的主要特征/属性是什么?
- 如何使用我的技术栈来表示它们?
不使用 Prisma ORM 的数据建模
数据建模通常需要在(至少)两个层面进行:
- 在数据库层面
- 在应用程序层面(即,在你的编程语言中)
应用程序模型在这两个层面上的表示方式可能因以下几个原因而不同:
- 数据库和编程语言使用不同的数据类型
- 关系在数据库中的表示方式与在编程语言中的表示方式不同
- 数据库通常具有更强大的数据建模能力,例如索引、级联删除或各种附加约束(例如唯一、非空等)
- 数据库和编程语言有不同的技术约束
数据库层面的数据建模
关系型数据库
在关系型数据库中,模型由 表 表示。例如,你可以定义一个 users
表来存储应用程序的用户信息。使用 PostgreSQL,你可以按如下方式定义它:
CREATE TABLE users (
user_id SERIAL PRIMARY KEY NOT NULL,
name VARCHAR(255),
email VARCHAR(255) UNIQUE NOT NULL,
isAdmin BOOLEAN NOT NULL DEFAULT false
);
包含一些随机数据的 users
表的可视化表示可能如下所示:
user_id | name | email | isAdmin |
---|---|---|---|
1 | Alice | alice@prisma.io | false |
2 | Bob | bob@prisma.io | false |
3 | Sarah | sarah@prisma.io | true |
它包含以下列:
user_id
:一个整数,users
表中每条新记录都会递增。它还表示每条记录的主键。name
:一个最多包含 255 个字符的字符串。email
:一个最多包含 255 个字符的字符串。此外,添加的约束表示email
列不允许存在重复值,并且每条记录都必须为其设置值。isAdmin
:一个布尔值,指示用户是否具有管理员权限(默认值:false
)
MongoDB
在 MongoDB 数据库中,模型由 集合 表示,并包含可以具有任何结构的 文档。
{
_id: '607ee94800bbe41f001fd568',
slug: 'prisma-loves-mongodb',
title: 'Prisma <3 MongoDB',
body: "This is my first post. Isn't MongoDB + Prisma awesome?!"
}
Prisma Client 当前期望一致的模型和 规范化模型设计。这意味着:
- 如果模型或字段不在 Prisma schema 中,它将被忽略
- 如果一个字段是必填的但在 MongoDB 数据集中不存在,你将收到一个错误
应用程序层面的数据建模
除了创建表示应用程序领域中实体的表之外,你还需要在编程语言中创建应用程序模型。在面向对象语言中,这通常通过创建 类 来表示你的模型。根据编程语言的不同,这也可以通过 接口 或 结构体 来完成。
数据库中的表与代码中定义的模型之间通常存在很强的关联性。例如,要在应用程序中表示上述 users
表中的记录,你可能会定义一个类似于这样的 JavaScript (ES6) 类:
class User {
constructor(user_id, name, email, isAdmin) {
this.user_id = user_id
this.name = name
this.email = email
this.isAdmin = isAdmin
}
}
使用 TypeScript 时,你可能会定义一个接口来代替:
interface User {
user_id: number
name: string
email: string
isAdmin: boolean
}
请注意,在两种情况下,User
模型都具有与前一个示例中 users
表相同的属性。虽然数据库表和应用程序模型之间通常存在 1:1 的映射关系,但也可能出现模型在数据库和应用程序中表示方式完全不同的情况。
通过此设置,你可以从 users
表中检索记录,并将其存储为 User
类型的实例。以下示例代码片段使用 pg
作为 PostgreSQL 驱动,并根据上面定义的 JavaScript 类创建了一个 User
实例:
const resultRows = await client.query('SELECT * FROM users WHERE user_id = 1')
const userData = resultRows[0]
const user = new User(
userData.user_id,
userData.name,
userData.email,
userData.isAdmin
)
// user = {
// user_id: 1,
// name: "Alice",
// email: "alice@prisma.io",
// isAdmin: false
// }
请注意,在这些示例中,应用程序模型是“哑”的,这意味着它们不实现任何逻辑,其唯一目的是以 *普通 JavaScript 对象* 的形式携带数据。
使用 ORM 进行数据建模
ORM 通常用于面向对象语言,以方便开发者使用数据库。ORM 的关键特性是它允许你以 *类* 的形式对应用程序数据进行建模,这些类被映射到底层数据库中的 *表*。
与上述方法相比,主要区别在于这些类不仅承载数据,还实现了大量的逻辑。主要用于存储、检索、序列化和反序列化,但有时它们也会实现特定于你的应用程序的业务逻辑。
这意味着,你无需编写 SQL 语句来读写数据库中的数据,而是你的模型类实例提供了一个 API 来存储和检索数据。
Sequelize 是 Node.js 生态系统中一个流行的 ORM,以下是如何使用 Sequelize 的建模方法定义前面章节中相同的 User
模型:
class User extends Model {}
User.init(
{
user_id: {
type: Sequelize.INTEGER,
primaryKey: true,
autoIncrement: true,
},
name: Sequelize.STRING(255),
email: {
type: Sequelize.STRING(255),
unique: true,
},
isAdmin: Sequelize.BOOLEAN,
},
{ sequelize, modelName: 'user' }
)
要使这个 User
类示例正常工作,你仍然需要在数据库中创建相应的表。使用 Sequelize,你有两种方法可以做到这一点:
- 运行
User.sync()
(通常不推荐用于生产环境) - 使用 Sequelize 迁移 来更改你的数据库 schema
请注意,你永远不会手动实例化 User
类(使用 new User(...)
),如上一节所示,而是调用 User
类的 静态 方法,这些方法会返回 User
模型实例。
const user = await User.findByPk(42)
对 findByPk
的调用会创建一个 SQL 语句,用于检索由 ID 值 42
标识的 User
记录。
生成的 user
对象是 Sequelize 的 Model
类的实例(因为 User
继承自 Model
)。它不是一个 POJO,而是一个实现了 Sequelize 额外行为的对象。
使用 Prisma ORM 进行数据建模
根据你希望在应用程序中使用 Prisma ORM 的哪些部分,数据建模流程会略有不同。以下两节解释了仅使用 Prisma Client 和使用 Prisma Client 和 Prisma Migrate 的工作流程。
无论采用哪种方法,使用 Prisma ORM 都不会通过手动定义类、接口或结构体在编程语言中创建应用程序模型。相反,应用程序模型是在你的 Prisma schema 中定义的。
- 仅使用 Prisma Client:Prisma schema 中的应用程序模型是*根据数据库 schema 的内省生成的*。数据建模主要在数据库层面进行。
- Prisma Client 和 Prisma Migrate:数据建模在 Prisma schema 中通过*手动添加应用程序模型*来完成。Prisma Migrate 将这些应用程序模型映射到底层数据库中的表(目前仅支持关系型数据库)。
例如,前一个示例中的 User
模型在 Prisma schema 中将表示如下:
model User {
user_id Int @id @default(autoincrement())
name String?
email String @unique
isAdmin Boolean @default(false)
}
一旦应用程序模型在你的 Prisma schema 中(无论是通过内省还是手动添加),下一步通常是生成 Prisma Client,它提供一个编程和类型安全的 API,用于以你的应用程序模型的形状读写数据。
Prisma Client 使用 TypeScript 类型别名 来表示代码中的应用程序模型。例如,User
模型在生成的 Prisma Client 库中将表示如下:
export type User = {
id: number
name: string | null
email: string
isAdmin: boolean
}
除了生成的类型,Prisma Client 还提供了一个数据访问 API,你可以在安装 @prisma/client
包后使用它。
import { PrismaClient } from '@prisma/client'
// or
// const { PrismaClient } = require('@prisma/client')
const prisma = new PrismaClient()
// use inside an `async` function to `await` the result
await prisma.user.findUnique(...)
await prisma.user.findMany(...)
await prisma.user.create(...)
await prisma.user.update(...)
await prisma.user.delete(...)
await prisma.user.upsert(...)
仅使用 Prisma Client
当你的应用程序中仅使用 Prisma Client 而*不*使用 Prisma Migrate 时,数据建模需要通过 SQL 在数据库层面进行。一旦你的 SQL schema 准备就绪,你就可以使用 Prisma 的内省功能将应用程序模型添加到你的 Prisma schema 中。最后,你生成 Prisma Client,它会为你创建类型以及用于读写数据库数据的编程 API。
以下是主要工作流程的概述:
- 使用 SQL 更改你的数据库 schema(例如
CREATE TABLE
,ALTER TABLE
, ...) - 运行
prisma db pull
来内省数据库并将应用程序模型添加到 Prisma schema - 运行
prisma generate
来更新你的 Prisma Client API
使用 Prisma Client 和 Prisma Migrate
使用 Prisma Migrate 时,你在 Prisma schema 中定义你的应用程序模型,对于关系型数据库,使用 prisma migrate
子命令生成纯 SQL 迁移文件,你可以在应用之前对其进行编辑。对于 MongoDB,你使用 prisma db push
来代替,它会直接将更改应用到你的数据库。
以下是主要工作流程的概述:
- 在 Prisma schema 中手动更改你的应用程序模型(例如,添加新模型、删除现有模型等)
- 运行
prisma migrate dev
创建并应用迁移,或运行prisma db push
直接应用更改(两种情况下都会自动生成 Prisma Client)