跳到主要内容

TypeORM 与 Drizzle

问题

TypeORM 和 Drizzle ORM 的核心特性和使用方法是什么?

答案

一、TypeORM

TypeORM 是 Node.js/TypeScript 中的老牌 ORM,同时支持 Active Record 和 Data Mapper 两种模式。

Entity 定义

entities/User.ts
import {
Entity, PrimaryGeneratedColumn, Column,
OneToMany, CreateDateColumn, UpdateDateColumn, Index
} from 'typeorm';
import { Post } from './Post';

@Entity('users')
export class User {
@PrimaryGeneratedColumn()
id: number;

@Column({ unique: true })
@Index()
email: string;

@Column({ nullable: true })
name: string | null;

@Column({ type: 'int', default: 0 })
age: number;

@Column({ type: 'enum', enum: ['USER', 'ADMIN'], default: 'USER' })
role: 'USER' | 'ADMIN';

// 一对多关系
@OneToMany(() => Post, (post) => post.author)
posts: Post[];

@CreateDateColumn()
createdAt: Date;

@UpdateDateColumn()
updatedAt: Date;
}
entities/Post.ts
import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, JoinColumn } from 'typeorm';
import { User } from './User';

@Entity('posts')
export class Post {
@PrimaryGeneratedColumn()
id: number;

@Column()
title: string;

@Column({ type: 'text', nullable: true })
content: string | null;

@Column({ default: false })
published: boolean;

// 多对一关系
@ManyToOne(() => User, (user) => user.posts)
@JoinColumn({ name: 'author_id' })
author: User;

@Column({ name: 'author_id' })
authorId: number;
}

CRUD 操作

const userRepo = dataSource.getRepository(User);

// 创建
const user = userRepo.create({ email: 'alice@example.com', name: 'Alice' });
await userRepo.save(user);

// 查询(解决 N+1:使用 relations)
const users = await userRepo.find({
relations: ['posts'],
where: { age: MoreThan(18) },
order: { createdAt: 'DESC' },
take: 10,
skip: 0,
});

// Query Builder(复杂查询)
const result = await userRepo
.createQueryBuilder('user')
.leftJoinAndSelect('user.posts', 'post')
.where('user.age > :age', { age: 18 })
.andWhere('post.published = :published', { published: true })
.orderBy('user.createdAt', 'DESC')
.skip(0).take(10)
.getManyAndCount(); // 返回 [data, totalCount]

// 更新
await userRepo.update({ id: 1 }, { name: 'Alice Updated' });

// Upsert
await userRepo.upsert(
{ email: 'alice@example.com', name: 'Alice' },
['email'] // 冲突键
);

// 删除
await userRepo.delete({ id: 1 });
// 软删除(需 @DeleteDateColumn)
await userRepo.softDelete({ id: 1 });

事务

await dataSource.transaction(async (manager) => {
const user = manager.create(User, { email: 'a@b.com', name: 'A' });
await manager.save(user);
const post = manager.create(Post, { title: 'Hello', authorId: user.id });
await manager.save(post);
});

迁移

# 生成迁移
npx typeorm migration:generate -d src/data-source.ts src/migrations/AddUserAge

# 执行迁移
npx typeorm migration:run -d src/data-source.ts

# 回滚
npx typeorm migration:revert -d src/data-source.ts
TypeORM 注意事项
  1. 装饰器反射:必须在 tsconfig.json 中启用 experimentalDecoratorsemitDecoratorMetadata
  2. 懒加载:TypeORM 支持懒加载(Promise<Post[]>),但容易引发 N+1 问题
  3. 连接管理:使用 DataSource 替代已废弃的 createConnection

二、Drizzle ORM

Drizzle 是新一代 TypeScript ORM,以 SQL-like 的 API 和零运行时依赖著称。

Schema 定义

src/db/schema.ts
import { pgTable, serial, text, integer, boolean, timestamp, pgEnum } from 'drizzle-orm/pg-core';
import { relations } from 'drizzle-orm';

// 枚举
export const roleEnum = pgEnum('role', ['USER', 'ADMIN']);

// 用户表
export const users = pgTable('users', {
id: serial('id').primaryKey(),
email: text('email').unique().notNull(),
name: text('name'),
age: integer('age').default(0).notNull(),
role: roleEnum('role').default('USER').notNull(),
createdAt: timestamp('created_at').defaultNow().notNull(),
updatedAt: timestamp('updated_at').defaultNow().notNull(),
});

// 文章表
export const posts = pgTable('posts', {
id: serial('id').primaryKey(),
title: text('title').notNull(),
content: text('content'),
published: boolean('published').default(false).notNull(),
authorId: integer('author_id').references(() => users.id).notNull(),
createdAt: timestamp('created_at').defaultNow().notNull(),
});

// 关系定义(与表定义分离)
export const usersRelations = relations(users, ({ many }) => ({
posts: many(posts),
}));

export const postsRelations = relations(posts, ({ one }) => ({
author: one(users, { fields: [posts.authorId], references: [users.id] }),
}));

CRUD 操作

import { db } from './db';
import { users, posts } from './db/schema';
import { eq, gt, and, like, desc, sql, count } from 'drizzle-orm';

// 创建
const [newUser] = await db.insert(users)
.values({ email: 'alice@example.com', name: 'Alice' })
.returning(); // PostgreSQL 支持 RETURNING

// 批量创建
await db.insert(users).values([
{ email: 'bob@example.com', name: 'Bob' },
{ email: 'charlie@example.com', name: 'Charlie' },
]);

// 查询(SQL-like API)
const result = await db.select()
.from(users)
.where(and(
gt(users.age, 18),
like(users.email, '%@gmail.com')
))
.orderBy(desc(users.createdAt))
.limit(10)
.offset(0);

// JOIN 查询
const usersWithPosts = await db.select({
userId: users.id,
userName: users.name,
postTitle: posts.title,
}).from(users)
.leftJoin(posts, eq(users.id, posts.authorId))
.where(eq(posts.published, true));

// 关系查询(需要 Drizzle Relations API)
const usersWithAllPosts = await db.query.users.findMany({
with: { posts: true },
where: gt(users.age, 18),
limit: 10,
});

// 聚合
const stats = await db.select({
role: users.role,
userCount: count(users.id),
}).from(users).groupBy(users.role);

// 更新
await db.update(users)
.set({ name: 'Alice Updated', age: sql`${users.age} + 1` })
.where(eq(users.id, 1));

// Upsert
await db.insert(users)
.values({ email: 'alice@example.com', name: 'Alice' })
.onConflictDoUpdate({
target: users.email,
set: { name: 'Alice Updated' },
});

// 删除
await db.delete(users).where(eq(users.id, 1));

事务

await db.transaction(async (tx) => {
const [user] = await tx.insert(users)
.values({ email: 'a@b.com', name: 'A' })
.returning();
await tx.insert(posts)
.values({ title: 'Hello', authorId: user.id });
});

迁移

# 生成迁移
npx drizzle-kit generate

# 应用迁移
npx drizzle-kit migrate

# 直接推送(开发环境)
npx drizzle-kit push

三、TypeORM vs Drizzle 对比

维度TypeORMDrizzle
Schema 定义装饰器(类)函数式(TypeScript 函数)
API 风格Repository + Query BuilderSQL-like 函数调用
类型安全中等(装饰器有运行时黑魔法)极高(纯 TypeScript 推导)
Bundle 大小~800KB~50KB(零依赖)
学习曲线中等(了解装饰器语法)低(接近原生 SQL)
生态成熟度高(2016 年起,社区大)中(2022 年起,增长快)
数据库支持MySQL、PG、SQLite、MSSQL、MongoDBMySQL、PG、SQLite
Edge 兼容不支持支持(Cloudflare D1、Vercel Edge)

常见面试问题

Q1: TypeORM 的 Active Record 和 Data Mapper 有什么区别?

答案

// Active Record:Model 类直接调用
class User extends BaseEntity { /* ... */ }
const user = new User();
user.name = 'Alice';
await user.save(); // 模型自己保存自己

// Data Mapper:通过 Repository 操作
const userRepo = dataSource.getRepository(User);
const user = userRepo.create({ name: 'Alice' });
await userRepo.save(user); // Repository 负责持久化
  • Active Record 简单直接,适合小项目
  • Data Mapper 模型与数据库解耦,适合复杂业务、便于测试

Q2: Drizzle 为什么在 Edge Runtime 上更有优势?

答案

  1. 零依赖:不依赖 Node.js 特定 API,可在 Cloudflare Workers、Vercel Edge 运行
  2. 体积小:~50KB vs TypeORM ~800KB,冷启动更快
  3. 驱动适配:支持 D1、Turso 等 Edge 数据库驱动
  4. 无反射:不使用装饰器/反射,兼容任何 JavaScript 运行时

Q3: 什么时候选 TypeORM、什么时候选 Drizzle?

答案

场景推荐
传统 NestJS 后端TypeORM(官方集成好)
Next.js / Edge 项目Drizzle
团队熟悉 SQLDrizzle(API 接近 SQL)
需要丰富的 ORM 特性TypeORM(懒加载、监听器等)
新项目、追求轻量Drizzle
需要 MongoDB 支持TypeORM

相关链接