Drizzle ORM 详解
Drizzle ORM 是一个为 TypeScript/JavaScript 设计的现代、轻量级、完全类型安全 (fully type-safe) 的 ORM (Object Relational Mapper)。它旨在提供一个高度接近 SQL 的 API,同时利用 TypeScript 的类型系统,在编译时捕获数据库相关的错误,并在运行时生成高效的 SQL 语句。Drizzle ORM 强调性能、开发者体验和对底层 SQL 的透明度。
核心思想:将数据库 schema 定义为 TypeScript 代码,并通过其表达式语言在编译时实现对 SQL 查询的完全类型安全检查,同时保持生成 SQL 的高效性与可读性。 它更像是一个类型安全的 SQL 查询构建器,而非传统的重型 ORM。
重要提示: Drizzle ORM 是为 TypeScript/JavaScript 生态系统设计的。因此,本文档中的所有代码示例都将使用 TypeScript 语言。这与之前关于 Go 或 Python 库的示例有所不同,以确保示例的实用性和相关性。
一、为什么需要 Drizzle ORM?
在 TypeScript/JavaScript 应用中与关系型数据库交互时,开发者常面临以下痛点:
- 类型安全缺失:传统上,SQL 字符串是在运行时构造和执行的,无法在编译时检查列名拼写错误、类型不匹配等问题,容易导致运行时错误。
- ORM 的“魔术”和性能开销:许多传统 ORM 隐藏了底层 SQL 的复杂性,虽然简化了开发,但可能生成低效的 SQL,且难以调试。开发者对生成的查询缺乏控制。
- 模式同步与迁移:手动管理数据库模式的创建和变更(迁移)过程繁琐且容易出错。
- 跨数据库兼容性:不同的数据库有细微的 SQL 语法差异,需要 ORM 进行适配。
- 开发者体验:希望在享受 TypeScript 带来的类型安全和自动补全的同时,能以接近原生 SQL 的方式操作数据库。
Drizzle ORM 旨在通过以下方式解决这些问题:
- 编译时类型安全:从数据库模式到查询结果,整个过程都是类型安全的。在编写查询时即可获得自动补全和错误提示。
- “SQL-like” API:提供一个高度接近 SQL 语法的表达式语言,让熟悉 SQL 的开发者能够快速上手,并对生成的 SQL 拥有更大控制权。
- 轻量与高性能:生成简洁高效的 SQL,没有过多的运行时开销。
- Drizzle Kit CLI:提供强大的命令行工具,用于数据库模式内省 (introspection)、迁移生成和管理。
- 多数据库支持:支持 PostgreSQL (Neon, Supabase, Vercel Postgres, AWS RDS), MySQL, SQLite (Bun SQLite, Turso, Cloudflare D1)。
二、Drizzle ORM 的核心概念
Drizzle ORM 围绕几个关键概念构建,理解它们是高效使用 Drizzle 的基础。
Schema Definition (模式定义):
- 定义:使用 Drizzle 提供的 DSL (Domain Specific Language) 在 TypeScript 代码中定义数据库的表、列、索引和关系。
- 作用:作为事实的单一来源 (Single Source of Truth),用于生成 SQL 语句、进行类型推断和生成数据库迁移。
DB Instance (数据库实例):
- 定义:通过 Drizzle 的
drizzle-orm包提供的drizzle()函数,传入数据库驱动的连接对象,创建的 Drizzle ORM 数据库客户端实例。 - 作用:是执行所有 Drizzle ORM 操作的入口点。
- 定义:通过 Drizzle 的
Drizzle Kit (CLI 工具):
- 定义:一个命令行工具,用于处理 Drizzle ORM 的模式管理任务。
- 作用:内省现有数据库模式并生成 TypeScript Schema 文件;生成数据库迁移文件以同步代码中的 Schema 变更到数据库;推送 Schema 变更到数据库。
SQL Expression Language (SQL 表达式语言):
- 定义:Drizzle ORM 提供的用于构建查询的 TypeScript 函数和操作符集合,它们与 SQL 关键字和操作符一对一对应。
- 作用:在 TypeScript 代码中以类型安全的方式编写
SELECT,INSERT,UPDATE,DELETE等 SQL 语句。
Relations (关系):
- 定义:在 Schema 文件中定义表之间的关系,如一对一、一对多、多对多。
- 作用:允许在查询时轻松地
with相关联的数据,进行连接查询。
Transactions (事务):
- 定义:一组原子性的数据库操作,要么全部成功,要么全部失败。
- 作用:确保数据的一致性和完整性。
三、Drizzle ORM 架构与工作流程
Drizzle ORM 的设计理念是**“编译器优先” (Compiler-First)**,这意味着它在编译时利用 TypeScript 的类型系统进行大量检查和推断。
3.1 架构图
graph TD
subgraph Application
AppCode[TypeScript/JavaScript Application Code]
end
subgraph Drizzle ORM Framework
AppCode --> SchemaDef["Schema Definition (.ts files)"]
AppCode --> DrizzleORM["Drizzle ORM API (select, insert, update, delete, etc.)"]
DrizzleORM -- "Generates" --> SQLStatements[Optimized SQL Statements]
end
subgraph Drizzle Kit CLI
direction LR
SchemaDef -->|Used by| DrizzleKit[Drizzle Kit CLI]
ExistingDB[(Existing Database)] -->|Introspects & Generates Schema| DrizzleKit
DrizzleKit -->|Generates/Pushes| Migrations[Database Migrations]
end
subgraph Database Interaction
SQLStatements -- "Executed by" --> DBClient["Database Driver Client (e.g., node-postgres, better-sqlite3)"]
DBClient <--> Database["Relational Database (PostgreSQL, MySQL, SQLite)"]
end
AppCode -- "Creates & Uses" --> DBClient
Migrations --> Database
3.2 工作流程 (Schema 到数据操作)
一个典型的 Drizzle ORM 工作流程如下:
sequenceDiagram
participant Dev as 开发者
participant Schema as Schema 定义 (.ts)
participant DrizzleKit as Drizzle Kit CLI
participant DB as 数据库
participant App as 应用程序
Dev->>Schema: 1. 定义数据库 Schema (Tables, Columns, Relations)
Dev->>DrizzleKit: 2. 生成初始迁移 (drizzle-kit generate)
DrizzleKit->>DB: 3. 应用迁移到数据库 (drizzle-kit migrate / push)
Dev->>App: 4. 在应用程序中实例化 Drizzle DB 客户端
App->>App: 5. 编写 Drizzle ORM 查询 (select, insert, update, delete)
Note over App: TypeScript 编译器在编写时提供类型安全和自动补全
App->>DB: 6. Drizzle ORM 生成并执行 SQL 语句
DB-->>App: 7. 返回原始数据库结果
App->>App: 8. Drizzle ORM 将结果类型化,供应用程序使用
四、Drizzle ORM 入门与基本用法
4.1 安装
1 | # 安装核心 Drizzle ORM 库 |
4.2 配置 drizzle.config.ts
在项目根目录创建 drizzle.config.ts (或 .js) 文件,用于 Drizzle Kit 配置。
1 | // drizzle.config.ts |
4.3 数据库连接与 Schema 定义
首先,在 src/db.ts 或类似文件中设置数据库连接:
1 | // src/db.ts |
然后,在 src/schema.ts 定义你的数据库模式:
1 | // src/schema.ts |
五、Drizzle ORM 各种 SQL 操作方法详解
以下示例将演示 Drizzle ORM 的 CRUD (Create, Read, Update, Delete) 操作,以及更高级的查询功能。
1 | // src/examples.ts (假设 db 实例已从 src/db.ts 导入) |
六、数据库迁移 (Drizzle Kit)
Drizzle Kit CLI 工具是 Drizzle ORM 生态系统的重要组成部分,它负责管理数据库模式的演进。
生成迁移文件:
当你修改了src/schema.ts文件后,运行以下命令会比较当前 Schema 和上次的快照,并生成一个新的迁移文件:1
npx drizzle-kit generate:pg # 或 generate:mysql, generate:sqlite
这会在
drizzle目录下创建一个带有时间戳的.ts文件,其中包含 Drizzle ORM 自动生成的 SQL 语句(如CREATE TABLE,ALTER TABLE)。应用迁移:
在你的应用程序中,你需要编写一个脚本来执行这些生成的迁移文件。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16// scripts/migrate.ts
import { migrate } from 'drizzle-orm/postgres-js/migrator';
import { db, closeDbConnection } from '../src/db';
async function main() {
console.log('Running migrations...');
await migrate(db, { migrationsFolder: './drizzle' }); // 传入 Drizzle 客户端实例和迁移文件目录
console.log('Migrations finished!');
await closeDbConnection();
process.exit(0);
}
main().catch((err) => {
console.error('Migration failed:', err);
process.exit(1);
});然后运行此脚本:
1
ts-node scripts/migrate.ts # 或 node scripts/migrate.js (如果已编译)
模式推送 (Schema Push):
对于开发环境,如果你想快速同步 Schema 变更而不需要生成迁移文件,可以使用push命令。它会直接将 Schema 状态推送到数据库(会删除不存在于 Schema 中的表)。注意:不推荐在生产环境使用。1
npx drizzle-kit push:pg # 或 push:mysql, push:sqlite
七、Drizzle ORM 的优缺点与适用场景
7.1 优点:
- 极致的类型安全:从 Schema 定义到查询结果,全程类型推断和检查,极大减少运行时错误。
- “SQL-like” API:学习曲线平缓,熟悉 SQL 的开发者可以快速上手,对生成的 SQL 有清晰的认识和控制。
- 高性能与轻量:生成的 SQL 简洁高效,运行时开销极小,是性能敏感应用的理想选择。
- Drizzle Kit CLI 强大:自动化 Schema 内省和迁移管理,提升开发效率。
- 现代化的开发体验:利用 TypeScript 的优势,提供优秀的自动补全、重构支持。
- 多数据库支持:广泛支持主流关系型数据库。
- 异步支持:原生支持
async/await,与现代 JS/TS 生态系统完美融合。
7.2 缺点:
- 相对新颖:相较于 TypeORM、Prisma 等成熟 ORM,Drizzle ORM 出现时间较晚,社区规模和生态系统仍在快速成长中。
- 非传统 ORM 理念:不提供 Active Record 模式,操作围绕 SQL 表达式展开,可能不适合偏爱高层抽象和对象模型的开发者。
- 缺乏缓存层:Drizzle ORM 是一个 SQL 查询构建器,它不提供内置的查询缓存或一级/二级缓存,这通常需要开发者自行实现或集成。
- 学习某些高级特性:虽然基础查询 SQL-like,但某些高级表达式(如复杂的 CTE、窗口函数)可能需要时间来适应 Drizzle 的 DSL 语法。
7.3 适用场景:
- 追求类型安全的 TypeScript/JavaScript 应用:无论是前端 (Next.js server components, Remix) 还是后端 (Node.js, Bun)。
- 对性能有较高要求,不希望 ORM 引入额外开销的项目。
- 偏好 SQL 语法,希望对底层查询有更强控制的开发者。
- 微服务架构:轻量级和高性能的特点使其成为微服务中数据访问层的理想选择。
- 需要频繁管理数据库模式变更的项目:Drizzle Kit 的迁移功能非常便捷。
八、安全性考虑
使用 Drizzle ORM 开发数据库应用时,虽然 Drizzle 本身在防止某些攻击方面提供了帮助,但开发者仍需关注以下安全实践:
- SQL 注入防护 (由 Drizzle 自动处理):
- Drizzle ORM 的查询构建器会自动参数化查询,这意味着它会将所有动态值作为参数传递给数据库,而不是直接拼接到 SQL 字符串中。这从根本上防止了 SQL 注入攻击。
- 即使使用
sql模板字面量执行原始 SQL,Drizzle 也支持安全的参数化方式:sqlSELECT * FROM users WHERE name = ${userName}。
- 数据库连接字符串安全:
- 绝不将数据库连接字符串硬编码到代码中。
- 使用环境变量 (
.env文件, 操作系统环境变量) 来存储敏感的连接信息。 - 在生产环境中,应使用专门的秘密管理服务 (如 AWS Secrets Manager, Azure Key Vault, HashiCorp Vault)。
- 输入验证与过滤:
- 在将用户输入传递给 Drizzle ORM 查询之前,始终在应用程序层进行严格的输入验证和清理。这有助于防止意外的或恶意的数据进入数据库,例如:
- 检查数据类型、长度、格式。
- 防止 XSS (Cross-Site Scripting) 攻击 (如果数据最终会显示在前端)。
- 在将用户输入传递给 Drizzle ORM 查询之前,始终在应用程序层进行严格的输入验证和清理。这有助于防止意外的或恶意的数据进入数据库,例如:
- 最小权限原则:
- 为应用程序连接数据库所使用的用户账号,配置最小必要权限。例如,如果应用只需要读写特定表,不要赋予其删除整个数据库的权限。
- 事务管理:
- 对于涉及多个相互依赖的数据库操作,务必使用 Drizzle ORM 提供的事务 (
db.transaction()) 来确保数据的一致性和原子性,防止部分操作成功而导致数据不完整。
- 对于涉及多个相互依赖的数据库操作,务必使用 Drizzle ORM 提供的事务 (
- 错误处理:
- 捕获并适当地处理数据库操作可能抛出的错误,避免向用户暴露敏感的数据库错误信息。
- 依赖项安全:
- 保持 Drizzle ORM 及其数据库驱动 (
pg,mysql2,better-sqlite3等) 更新到最新版本,以获取安全补丁和性能改进。
- 保持 Drizzle ORM 及其数据库驱动 (
九、总结
Drizzle ORM 凭借其独特的 TypeScript 优先、类型安全和 SQL-like 的设计理念,正在迅速成为现代 JavaScript/TypeScript 数据库开发的有力竞争者。它成功地在 ORM 的易用性和原生 SQL 的控制力之间取得了平衡,同时提供了卓越的性能和开发者体验。对于追求代码质量、类型安全和数据库操作透明度的项目,Drizzle ORM 提供了一个强大而优雅的解决方案。理解并熟练运用其 Schema 定义、SQL 表达式语言以及 Drizzle Kit 工具,将极大地提升你的数据层开发效率和应用健壮性。
