TypeORM vs Prisma 选型对比
一、核心差异
两者的根本区别在于代码与数据库 Schema 的关系:
- TypeORM(代码优先):用 TypeScript 类和装饰器定义 Entity,ORM 从代码推断 Schema
- Prisma(Schema 优先):先写
.prisma文件定义 Schema,Prisma 生成客户端代码
typescript
// TypeORM 方式:实体类即是 Schema 定义
@Entity('users')
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column({ unique: true })
email: string;
@Column({ nullable: true })
name: string;
@OneToMany(() => Post, post => post.author)
posts: Post[];
}prisma
// Prisma 方式:独立的 Schema 文件
model User {
id Int @id @default(autoincrement())
email String @unique
name String?
posts Post[]
}二、全面对比
| 维度 | TypeORM | Prisma |
|---|---|---|
| 定义方式 | 装饰器 + 类(代码优先) | Schema 文件(schema 优先) |
| 类型安全 | 中(关系查询返回 any) | 强(完整推断的客户端类型) |
| 查询 API | Repository + QueryBuilder | Prisma Client(链式调用) |
| 关系查询 | 需显式 relations: ['posts'] | include: { posts: true } |
| 复杂查询 | QueryBuilder(灵活但冗长) | 有限,复杂时用 $queryRaw |
| 迁移工具 | 内置 CLI,手动管理 | 内置 CLI,自动生成 SQL |
| 数据库支持 | MySQL/PG/SQLite/MongoDB/等 | MySQL/PG/SQLite/MongoDB/SQL Server |
| N+1 问题 | 容易发生,需手动 join | 自动批处理(Dataloader) |
| 学习曲线 | 中(装饰器、QueryBuilder) | 低(Schema 直观,API 简单) |
| 社区成熟度 | 成熟,NestJS 官方集成 | 快速增长,成为新标准 |
| IDE 支持 | 良好 | 优秀(专用 VSCode 插件) |
三、TypeORM 的优势场景
1. 复杂动态查询
typescript
// QueryBuilder 支持条件动态构建
const qb = this.userRepo
.createQueryBuilder('user')
.leftJoinAndSelect('user.posts', 'post')
.where('user.isActive = :active', { active: true });
if (keyword) {
qb.andWhere('user.name LIKE :keyword OR user.email LIKE :keyword', {
keyword: `%${keyword}%`,
});
}
if (role) {
qb.andWhere('user.role = :role', { role });
}
const [users, total] = await qb
.orderBy('user.createdAt', 'DESC')
.skip((page - 1) * limit)
.take(limit)
.getManyAndCount();2. 需要精细控制 SQL
typescript
// 自定义事件监听(软删除、审计日志)
@Entity()
export class Post {
@DeleteDateColumn()
deletedAt: Date; // TypeORM 自动处理软删除
@BeforeInsert()
setSlug() {
this.slug = slugify(this.title);
}
@AfterLoad()
computeReadingTime() {
this.readingTime = Math.ceil(this.content.split(' ').length / 200);
}
}3. 历史项目迁移
已有 TypeORM Entity 定义的项目,迁移成本低。
四、Prisma 的优势场景
1. 极致类型安全
typescript
// Prisma Client 完全类型化,IDE 可以提示每个字段
const user = await prisma.user.findUnique({
where: { email: 'alice@prisma.io' },
include: {
posts: {
where: { published: true },
orderBy: { createdAt: 'desc' },
take: 5,
},
},
});
// user.posts 的类型是精确推断的,不是 any
user.posts[0].title; // ✅ IDE 有提示2. Schema 即文档
prisma
/// 用户实体
model User {
id Int @id @default(autoincrement())
email String @unique
/// 用户角色:user=普通用户,admin=管理员
role Role @default(USER)
/// 软删除时间
deletedAt DateTime?
createdAt DateTime @default(now())
posts Post[]
@@index([email, role])
@@map("users") // 数据库表名
}
enum Role {
USER
ADMIN
MODERATOR
}Schema 文件清晰展示了完整的数据结构、约束、索引——团队新成员一眼就能理解数据库设计。
3. 避免 N+1 问题
typescript
// Prisma 自动处理批处理,一次查询获取所有关联数据
const usersWithPosts = await prisma.user.findMany({
include: { posts: true }, // 自动 JOIN,不会 N+1
});
// TypeORM 中如果忘记 join,会触发 N+1
const users = await userRepo.find();
for (const user of users) {
user.posts = await postRepo.find({ where: { authorId: user.id } });
// ↑ 每个 user 一次查询,N 个用户 = N+1 次查询!
}五、性能对比
| 场景 | TypeORM | Prisma |
|---|---|---|
| 简单 CRUD | 相当 | 相当 |
| 复杂多表 JOIN | QueryBuilder 灵活控制 | 需要 $queryRaw |
| 批量插入 | manager.save([...]) | createMany |
| N+1 查询风险 | 高(默认懒加载) | 低(include 显式控制) |
| 冷启动时间 | 较快 | 较慢(Rust 引擎初始化) |
六、选型决策指南
新项目?
├── 团队熟悉 TypeScript 装饰器? → TypeORM
├── 追求最强类型安全和开发体验? → Prisma
└── 不确定? → Prisma(现代选择)
旧项目迁移?
└── 已有 TypeORM 实体? → 继续 TypeORM
查询需求?
├── 复杂动态查询、多条件过滤? → TypeORM QueryBuilder 更灵活
├── 标准 CRUD + 关系查询? → 两者都行,Prisma API 更简洁
└── 需要原生 SQL 复杂查询? → 两者都支持(TypeORM: rawQuery, Prisma: $queryRaw)七、本仓库实战策略
模块三的实战项目同时实现两个版本,通过对比直观感受差异:
practice/03-crud-app/typeorm-version/— TypeORM 版本(含 JWT 认证)practice/03-crud-app/prisma-version/— Prisma 版本(相同功能,不同 ORM)
建议两个版本都运行一遍,对比:
- Entity/Schema 定义的代码量和可读性
- 分页查询的实现方式
- 事务处理的 API 差异
- 迁移工作流
常见错误
| 错误 | 原因 | 解决 |
|---|---|---|
| TypeORM 和 Prisma 混用导致冲突 | 两个 ORM 各自维护连接池 | 同一项目只选一个 ORM |
| ORM 连接字符串格式错误 | 不同数据库驱动格式不同 | PostgreSQL: postgres://user:pass@host:5432/db;MySQL: mysql://... |
| 开发时频繁报"连接已关闭" | 连接池耗尽或超时 | 调整 max(连接数)和 idleTimeoutMillis,开发环境用单连接 |