什么是 ORM?应用如何与数据库对话,一文讲清
ORM(对象关系映射器)是一种代码库,位于应用和关系型数据库之间。开发者不必用 SQL 编写数据库查询,而是用编程语言中的普通对象来处理数据,例如 db.booking.create(...) 或 Booking.find(...)。ORM 会把这些操作翻译成 SQL、执行,再把结果转换回对象。常见的例子包括用于 TypeScript 的 Prisma 和 Drizzle、用于 Python 的 SQLAlchemy,以及用于 C# 的 Entity Framework Core。
用 AI 构建应用时,为什么 ORM 很重要
当 AI 应用构建工具为你的应用添加数据库时,它通常也会添加一个 ORM 来与之通信。你不必自己编写 ORM 代码,但了解它的存在仍然很有帮助:
- 你的数据模型存在于代码中。 ORM 的 schema 文件通常是描述应用数据最清晰的地方:有哪些表、每张表有哪些字段,以及它们如何关联。阅读它是快速检查 AI 是否理解了你的应用的好办法。
- 数据库的变更通过迁移完成。 当你提出“给客户添加一个电话号码”时,AI 会更新模型并创建一个迁移。了解这一点,有助于你明白为什么某项变更在上线前可能需要先被应用。
- 默认安全。 ORM 以一种能防范 SQL 注入的方式构建查询。SQL 注入是手工拼接查询的应用中最常见的安全漏洞之一。
- 性能陷阱。 ORM 很容易让你不经意间执行数百次小查询,而其实一次就够。这是 AI 构建的应用页面变慢的常见原因。
一个日常类比
想象一位英国旅行者和一位日本店主,两人语言不通,靠一位翻译交流。
旅行者(你的应用)说:“我想要你们最便宜的三种茶。”翻译(ORM)把这句话准确地译成日语(SQL),交给店主(数据库),再把答复译回英语。
旅行者无需学会日语,对大多数对话来说这没问题。但在复杂的谈判中,直接开口交流会更有帮助,这也是大多数 ORM 允许开发者在需要时编写原始 SQL 的原因。
ORM 如何工作
像 Postgres 这样的数据库把数据存放在由行和列组成的表中,并使用 SQL 进行查询。编程语言则使用对象来工作:例如一个带有 date、customer 和 status 的预订。ORM 负责在两者之间建立映射:
| 数据库端 | ORM / 代码端 |
|---|---|
表 bookings | 模型 Booking |
| 行 | 一个预订对象 |
列 starts_at | 字段 startsAt |
外键 customer_id | 关系 booking.customer |
SELECT、INSERT、UPDATE、DELETE | find、create、update、delete 方法 |
这四种操作就是几乎所有应用都依赖的 CRUD 操作。
实例演示:用 Prisma 处理预订
下面是一个为美发沙龙预订应用编写的简化版 Prisma schema,它描述了两张表以及它们之间的关联:
model Customer {
id Int @id @default(autoincrement())
name String
email String @unique
bookings Booking[]
}
model Booking {
id Int @id @default(autoincrement())
startsAt DateTime
service String
customer Customer @relation(fields: [customerId], references: [id])
customerId Int
}
用通俗的话说:一位客户有姓名、唯一的邮箱,并且可以有多次预订;每次预订有开始时间和服务项目,并且只属于一位客户。
现在应用需要查询“Lena 即将到来的预订,最早的排在前面”:
const bookings = await db.booking.findMany({
where: {
customer: { email: "lena@example.com" },
startsAt: { gte: new Date() },
},
orderBy: { startsAt: "asc" },
});
ORM 会把它转换成大致如下的 SQL,执行后返回一个预订对象列表:
SELECT b.* FROM "Booking" b
JOIN "Customer" c ON c.id = b."customerId"
WHERE c.email = $1 AND b."startsAt" >= $2
ORDER BY b."startsAt" ASC;
其中 $1 和 $2 是占位符:邮箱和日期与查询语句分开发送,这正是防止 SQL 注入的原因。
迁移
如果你接着提出“让客户可以添加电话号码”,AI 会在 Customer 模型中加入 phone String?(? 表示可选),并生成一个迁移,也就是类似 ALTER TABLE "Customer" ADD COLUMN "phone" TEXT; 的小型 SQL 脚本。迁移按顺序保存,因此每一份数据库副本都可以被同步到相同的结构,并且变更历史会被保留。
各语言常用的 ORM
| 语言 | 常用 ORM |
|---|---|
| TypeScript / JavaScript | Prisma、Drizzle、TypeORM、Sequelize |
| Python | SQLAlchemy、Django ORM、SQLModel |
| C# | Entity Framework Core |
| Java | Hibernate(通常通过 Spring Data JPA 使用) |
| Ruby | Active Record |
ORM 对 SQL 的隐藏程度各不相同。Prisma 使用自己的 schema 语言和查询风格;Drizzle 则刻意贴近 SQL 的结构,同时保持全部类型化。另外一类较轻量的工具叫做查询构建器,例如 TypeScript 中的 Knex 或 Kysely,它们帮助你在代码中构建 SQL,但不会把行映射为完整的对象。
关键术语
| 术语 | 含义 |
|---|---|
| 模型(Model) | 对某张表及其字段的代码定义。 |
| Schema | 对所有表、字段和关系的完整描述。 |
| 关系(Relation) | 模型之间的链接,例如某次预订属于某位客户。 |
| 迁移(Migration) | 用于修改数据库结构的带版本号的脚本。 |
| 查询构建器(Query builder) | 一种较轻量的工具,用于在代码中构建 SQL,而不做完整的对象映射。 |
| N+1 问题 | 先执行一次查询获取列表,再为每一项各执行一次查询——数据量大时会很慢。 |
| 原始 SQL(Raw SQL) | 手写的 SQL,在 ORM 成为阻碍时使用。 |
常见误解
- “ORM 就是数据库。” 并不是。它是一个翻译器。你仍然需要像 Postgres 这样的数据库。
- “用了 ORM 就不需要理解你的数据。” 设计糟糕的表无论通过 ORM 还是其他方式使用,都同样令人头疼。好的数据模型比工具本身更重要。
- “ORM 总是很慢。” 日常查询没有问题。慢通常来自 N+1 查询或缺失索引等模式,而这些都是可以修复的。
- “使用 ORM 就永远不会有 SQL 注入风险。” 标准的 ORM 方法是安全的,但如果把用户输入直接拼接进原始 SQL,仍然很危险。
- “修改 schema 文件就会改变线上数据库。” 只有在创建迁移并将其应用到该数据库之后才会如此。
向你的 AI 构建工具提出的要求
- “展示数据库 schema,并用通俗的语言解释每张表和每个关系。”
- “这个项目使用哪个 ORM?迁移文件在哪里?”
- “在修改任何表之前,先告诉我现有数据是否会受影响;未经询问,绝不删除仍有数据的字段。”
- “检查最慢的页面是否存在 N+1 查询,并在列表被筛选或排序的字段上添加索引。”
- “确保每个使用用户输入的查询都通过 ORM 或使用参数——不要拼接 SQL 字符串。”
Mythex 中的 ORM
当你的应用需要保存数据时,Mythex 会为项目添加一个专用的 Postgres 数据库。你可以在聊天中提出需求,也可以使用 /database 命令。Mythex 会把连接信息注入工作区和已发布的应用中(数据库文档)。之后,由智能体编写读取和保存数据的代码。如果你有偏好,比如“使用 Prisma”“使用 SQLAlchemy”或“使用原生 SQL,不要 ORM”,请直接在聊天中说明。你随时可以让它为你讲解 schema,也可以在代码编辑器中查看这些文件。想了解更全面的内容,请参阅什么是数据库和 SQL 与 NoSQL 对比。
常见问题
用简单的话说,什么是 ORM?
ORM(对象关系映射器)是一种代码库,位于你的应用和关系型数据库之间。开发者可以用编程语言中的普通对象来读写数据,ORM 则在幕后替他们编写 SQL 查询。
常见的 ORM 有哪些例子?
常见的 ORM 包括适用于 TypeScript 和 JavaScript 的 Prisma 与 Drizzle,适用于 Python 的 SQLAlchemy 和 Django ORM,适用于 C# 的 Entity Framework Core,适用于 Java 的 Hibernate,以及 Ruby on Rails 中的 Active Record。
ORM 比直接写 SQL 更好吗?
这取决于具体工作。ORM 让日常的读写更快编写、更安全、更易修改;而手写 SQL 则能为复杂报表或对性能要求高的查询提供完全的控制。大多数 ORM 在需要时都允许你直接使用原始 SQL。
什么是迁移(migration)?
迁移是一个小型的、带版本号的脚本,用于修改数据库结构,例如新增一张表或一个字段。ORM 可以根据你的数据模型生成迁移,让每一份数据库副本(无论是在笔记本电脑、测试环境还是生产环境中)都保持相同的结构。