数据建模
什么是数据建模?
术语数据建模指的是定义应用程序中对象的形状和结构的过程,这些对象通常被称为“应用程序模型”。在关系数据库(如 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 的 Introspection 生成。数据建模主要发生在数据库层面。
- 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 中(无论它们是通过 Introspection 添加的还是您手动添加的),下一步通常是生成 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 的 Introspection 功能将应用程序模型添加到您的 Prisma schema。最后,您生成 Prisma Client,它会为您创建类型以及程序化 API,以便您在数据库中读取和写入数据。
以下是主要工作流程的概述
- 使用 SQL 更改您的数据库 schema(例如
CREATE TABLE
、ALTER TABLE
等) - 运行
prisma db pull
以 Introspection 数据库并将应用程序模型添加到 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)