数据建模
什么是数据建模?
术语数据建模指的是定义应用程序中对象形状和结构的过程,这些对象通常称为“应用程序模型”。在关系型数据库(如 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 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: "[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 Schema中定义的。
- 仅使用 Prisma Client:Prisma Schema 中的应用程序模型是根据你数据库模式的内省生成的。数据建模主要在数据库级别进行。
- Prisma Client 和 Prisma Migrate:数据建模在 Prisma Schema 中进行,方法是手动向其中添加应用程序模型。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 都会自动生成)