数据建模
什么是数据建模?
术语数据建模指的是定义应用程序中对象形状和结构的过程,这些对象通常称为“应用程序模型”。在关系数据库(如 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 | [email protected] | false |
2 | Bob | [email protected] | false |
3 | Sarah | [email protected] | 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 模式中不存在模型或字段,则会忽略它
- 如果字段是必需的,但在 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: "[email protected]",
// 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 迁移 来更改您的数据库模式。
请注意,您永远不会手动实例化 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 模式 中定义的。
- 仅 Prisma Client:Prisma 模式中的应用程序模型是根据数据库模式的内省生成的。数据建模主要发生在数据库级别。
- Prisma Client 和 Prisma Migrate:通过手动向其中添加应用程序模型,在 Prisma 模式中进行数据建模。Prisma Migrate 将这些应用程序模型映射到底层数据库中的表(目前仅支持关系数据库)。
例如,前面示例中的 User
模型将在 Prisma 模式中表示如下:
model User {
user_id Int @id @default(autoincrement())
name String?
email String @unique
isAdmin Boolean @default(false)
}
一旦应用程序模型位于您的 Prisma 模式中(无论它们是通过内省添加的还是由您手动添加的),下一步通常是生成 Prisma Client,它提供了一个以编程方式且类型安全的方式读取和写入应用程序模型形状数据的 API。
Prisma Client 使用 TypeScript 类型别名 来在您的代码中表示您的应用程序模型。例如,User
模型将在生成的 Prisma Client 库中表示如下:
export declare 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 模式准备就绪,您就可以使用 Prisma 的内省功能将应用程序模型添加到您的 Prisma 模式中。最后,您生成 Prisma Client,它会为您创建类型以及以编程方式读取和写入数据库数据的 API。
以下是主要工作流程概述:
- 使用 SQL 更改您的数据库模式(例如
CREATE TABLE
、ALTER TABLE
等)。 - 运行
prisma db pull
以内省数据库并将应用程序模型添加到 Prisma 模式。 - 运行
prisma generate
以更新您的 Prisma Client API。
使用 Prisma Client 和 Prisma Migrate
当使用 Prisma Migrate 时,您在 Prisma 模式中定义您的应用程序,并使用关系数据库使用 prisma migrate
子命令生成纯 SQL 迁移文件,您可以在应用之前对其进行编辑。使用 MongoDB 时,您改为使用 prisma db push
,它会将更改直接应用到您的数据库。
以下是主要工作流程概述:
- 手动更改 Prisma 模式中的应用程序模型(例如,添加新模型、删除现有模型等)。
- 运行
prisma migrate dev
以创建和应用迁移,或运行prisma db push
以直接应用更改(在这两种情况下,Prisma Client 都会自动生成)。