对象关系映射(ORM)
ORM 把应用中的对象/模型操作映射为关系数据库查询与写入,降低常规数据访问样板,但不会替代 SQL、Schema、约束和查询性能知识。
[!info] related notes
- 所属 MOC: 数据库 MOC
- 前置概念: 关系型数据库, SQL
- 查询策略: ORM 的 Eager Loading 与 Lazy Loading, N+1 Query Problem
- Schema 演进: 数据库 Schema Migration
- 相关资源: Prisma 完整指南
对象关系映射(ORM)
一句话定义
ORM(Object-Relational Mapping)是在应用对象模型与关系数据库之间提供映射和数据访问抽象的一类工具:开发者操作 Model / Query API,ORM 负责生成 SQL、绑定参数、映射结果和管理部分关联行为。
两边在映射什么
Application Relational Database
----------- -------------------
Model / Entity ↔ Table
Object / Record ↔ Row
Property ↔ Column
Association ↔ FK / Join Table
Query API → SQL
这种映射降低了常规 CRUD 的样板代码,但对象世界和关系模型并不是完全同构的,因此会出现所谓 object-relational impedance mismatch。
ORM 能提供什么
不同 ORM 能力不同,常见包括:
- CRUD 与参数绑定;
- Model / Schema 定义;
- 关联关系;
- Eager / Lazy Loading;
- Transaction API;
- Migration 工具或集成;
- 类型生成与自动补全(部分 ORM 尤其强调这一点)。
Sequelize 与 Prisma 的风格差异
Sequelize
更接近传统 ORM:
Model class
hasMany / belongsTo / belongsToMany
include
它让表、关联和传统 ORM 概念更直接暴露出来。
Prisma
更偏 schema-first + generated client:
schema.prisma
→ generate typed client
→ prisma.user.findMany(...)
TypeScript 开发体验和查询结果类型推导通常更突出。
两者底层仍然面对同一套关系数据库问题:PK/FK、JOIN、约束、事务、索引与 Migration。
类型安全的边界
ORM 的 TypeScript 类型描述的是:
应用当前认为数据库长什么样。
它不等于真实数据库一定已经处于同一个 Schema 版本。例如 Prisma Client 已生成 user.status 类型,但生产 Migration 尚未执行时,TypeScript 仍可能编译通过,直到真实 SQL 查询缺失列才运行时报错。
ORM 不替代数据库约束
应用层 Model 可以写:
userId: number
但它不能证明 userId = 999 对应真实 User。最终引用完整性仍应由数据库 FOREIGN KEY 等机制负责。
同理:
TypeScript / ORM validation
→ 应用边界
Database constraint
→ 持久化边界
最大风险:隐藏查询成本
ORM 让数据访问看起来像对象操作:
await user.getAssessments()
但它可能意味着一次真实数据库往返。对 N 个对象逐个访问关联,很容易形成 N+1 Query Problem。
因此阅读 ORM 代码时始终要问:
这段代码最终生成什么 SQL?会发多少次查询?结果粒度是什么?
Raw SQL 仍然有价值
复杂统计、窗口函数、特定数据库能力、性能敏感查询或 ORM 无法清晰表达的查询,可以合理使用参数化 Raw SQL。成熟的 ORM 使用方式不是“永远不用 SQL”,而是让常规数据访问有统一抽象,同时保留深入到底层的能力。
边界与常见误解
- ORM 不是数据库,也不是 SQL 的替代语言。
- 换数据库通常不只是改连接字符串;方言、类型、索引、JSON、并发和 Migration 行为都可能不同。
- ORM 并不天然比 Raw SQL 慢;真正性能取决于它生成了什么查询、查询次数和执行计划。
- “ORM 防 SQL 注入”成立的前提是使用参数化 API;手工拼接 Raw SQL 仍然可能引入注入风险。