ORM:让你不用再手写SQL语句

最近用 Next.js 做了一个单词后台管理系统。作为后端新手,第一次接触到 ORM(Drizzle),也第一次亲手写出「不写 SQL 也能操作数据库」的代码。

这篇文章把我理解的东西讲清楚:ORM 是什么、为什么需要它、schema 怎么变成数据库表、Drizzle 和 Prisma 怎么选。


一、没有 ORM 的世界:手写 SQL

先看传统做法。你的系统要给数据库存用户、查用户,就得手写 SQL 字符串,拼到代码里:

ts 复制代码
// 手写 SQL:字符串拼查询,错一个字运行时才暴露
const result = await client.query(
  "SELECT id, name, email, password_hash FROM \"admin-users\" WHERE email = $1",
  [email]
);

问题很多:

  1. 容易拼错 ------ 表名、字段名、引号、占位符,错一个只能在运行时报错。
  2. SQL 注入风险 ------ 字符串拼接一不注意就被人注入攻击。
  3. 没有类型 ------ 查出来的 result 是任意的,字段是 string 还是 number,编译器根本不管。
  4. 数据库方言 ------ 换个数据库(Postgres → MySQL)语法就变,代码跟着重写。

写多了你会觉得:能不能用写普通代码的方式操作数据库,让工具去生成 SQL?

这就是 ORM 存在的意义。


二、什么是 ORM

ORM(Object Relational Mapping,对象关系映射) 是把「数据库里的表」和「编程语言里的对象/类型」互相转换的工具。

数据库是关系型的(表、行、列),代码是对象式的(类、对象、类型)。ORM 在中间当翻译官:你用对象的方式写代码,它翻译成 SQL 执行,再把结果翻译回对象。

用 ORM 之后,同一个查询长这样:

ts 复制代码
const user = await db.select()
  .from(adminUsers)                // 表,就是代码里的一个对象
  .where(eq(adminUsers.email, email));  // 列,就是对象上的一个属性

// user 是带类型的:写错字段名,编译器直接报错

对比一下:

手写 SQL ORM
写查询 拼字符串 链式方法调用
错字段名 运行时崩 编译期报错
类型 没有 完整推导
换数据库 重写 换驱动
注入风险 靠自己小心 参数自动转义

一句话:ORM 让你不用写 SQL,也能安全、有类型地操作数据库。


三、Schema:用代码表达表结构

ORM 的核心是 schema(数据库结构定义) ------ 在代码里描述「有哪些表、每张表有哪些列、有什么约束」。

我用的是 Drizzle,schema 就是一个普通的 TypeScript 文件。看我们项目里管理员表的定义:

ts 复制代码
export const adminUsers = pgTable("admin-users", {
  // TS属性名 : 类型构建器("数据库列名").修饰符链
  id: uuid("id").defaultRandom().primaryKey(),          // 主键,自动生成 uuid
  name: varchar("name", { length: 100 }).notNull(),      // 姓名,最长100,必填
  email: varchar("email", { length: 255 }).notNull().unique(), // 邮箱,唯一
  passwordHash: text("password_hash").notNull(),         // 密码哈希
  role: varchar("role", { length: 20 }).$type<AdminRole>().notNull(),
  createdAt: timestamp("created_at", { withTimezone: true }).defaultNow().notNull(),
  updatedAt: timestamp("updated_at", { withTimezone: true }).defaultNow().notNull(),
});

理解这段代码的关键是一个模式:

erlang 复制代码
TS属性名: 类型构建器("数据库列名").约束1.约束2...
   ↑              ↑              ↑
 代码里用      数据库里叫      默认值/主键/非空/唯一/外键...

几个容易迷糊的点:

  • uuid("id") ------ 括号里的 "id"数据库里真实的列名 ;左边的 id你在 TypeScript 里用到的属性名 。两者可以不一样(比如 passwordHash ↔ 数据库里的 password_hash)。
  • .$type<AdminRole>() ------ 纯 TS 层的约束。数据库里它还是普通 varchar(20),但代码里只允许赋 "system-admin" | "admin" 两种值,防止拼错字符串。
  • 索引 ------ 经常出现在 WHERE/JOIN 里的列值得加索引。Drizzle 里写在 pgTable 的第三个参数:
ts 复制代码
export const adminSessions = pgTable(
  "admin-session",
  { /* 列定义 */ },
  (table) => [index("admin_session_admin_id_idx").on(table.adminId)]
);

四、Schema 是怎么变成数据库表的?------ 迁移(Migration)

schema 只是「结构描述」,它本身不碰数据库 。真正建表要走两步,Drizzle 配套工具 drizzle-kit 负责:

arduino 复制代码
schema.ts (TS 定义)
    │  npm run db:generate     只生成,不执行
    ▼
drizzle/0001_xxx.sql            迁移文件(标准 SQL)
    │  npm run db:migrate       执行
    ▼
Postgres 数据库真实建表

generate 会对比「当前数据库的结构快照」和「schema.ts 的差异」,把差异生成为一段标准 SQL。比如我上面的 schema 生成出来是这样:

sql 复制代码
CREATE TABLE "admin-users" (
	"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
	"name" varchar(100) NOT NULL,
	"email" varchar(255) NOT NULL,
	CONSTRAINT "admin-users_email_unique" UNIQUE("email")
);

migrate 才把这 SQL 真的执行到数据库里。

所以:建表是一次性的迁移动作,而 ORM 每天干的活是建表之后的增删改查。


五、Drizzle vs Prisma:两个主流怎么选

TypeScript 生态里最常见两个 ORM:

Drizzle(我用的) Prisma
Schema 怎么定义 普通 TS 文件 独立的 .prisma 文件
需要代码生成 不需要 需要 prisma generate
API 风格 贴近 SQL(select().from().where() ORM 风格(prisma.user.findMany()
体积 / 性能 轻量、无魔法 较重、抽象层高
适合 对性能/体积敏感、喜欢可控 追求开发体验、功能全家桶

Drizzle 的哲学是「SQL 优先」:它的 API 几乎就是 SQL 的逐字翻译,schema 就是普通 TS 代码,没有代码生成那层神秘环节,学起来也更容易看懂底层在干嘛。

选型建议:如果项目对性能、包体积敏感,或者想弄明白底层原理,选 Drizzle;如果想开箱即用、少操心,选 Prisma。


小结

  1. ORM 是表和对象之间的翻译官,让你不写 SQL 也能安全操作数据库。
  2. Schema 是用代码表达表结构,TS属性名 : 类型构建器("列名").约束 是它的核心语法。
  3. 迁移 把 schema 变成 SQL 再执行建表,generate 生成、migrate 执行。
  4. Drizzle vs Prisma ------ 前者贴近 SQL、轻量;后者抽象高、功能全。
相关推荐
柒和远方2 小时前
V077:Next.js 后台的认证与权限防线:首个超级管理员的事务锁初始化、会话令牌哈希,与四道管理员保护规则
orm·next.js
__zRainy__2 天前
Node系列 · ORM:mysql 驱动程序
数据库·后端·mysql·node.js·orm
在水一缸1 个月前
深入浅出:Node.js 下一代 ORM 架构设计与实战解析
数据库·微服务·云原生·node.js·orm·架构设计
鱼听禅1 个月前
C#学习笔记-Entity Framework Core基础操作学习
c#·orm·ef core
jump_jump1 个月前
Kysely 源码解剖:类型安全 SQL 如何走到 AST
node.js·orm·源码阅读
咏方舟【长江支流】1 个月前
【开源】跨语言·跨平台·跨数据库(4) ——用宝框架 ORM 数据源适配器
数据库·开源·ai编程·orm·咏方舟-长江支流·用宝框架·用宝框架orm
咏方舟【长江支流】1 个月前
【开源】跨语言·跨平台·跨数据库(3) —— 为用宝框架增加ORM,让AI执行
数据库·人工智能·开源·ai编程·orm·咏方舟-长江支流·用宝框架
hubro2 个月前
.NET热门ORM真实的性能测试总结
测试·orm·性能化化