Skip to content

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[]
}

二、全面对比

维度TypeORMPrisma
定义方式装饰器 + 类(代码优先)Schema 文件(schema 优先)
类型安全中(关系查询返回 any强(完整推断的客户端类型)
查询 APIRepository + QueryBuilderPrisma 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 次查询!
}

五、性能对比

场景TypeORMPrisma
简单 CRUD相当相当
复杂多表 JOINQueryBuilder 灵活控制需要 $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)

建议两个版本都运行一遍,对比:

  1. Entity/Schema 定义的代码量和可读性
  2. 分页查询的实现方式
  3. 事务处理的 API 差异
  4. 迁移工作流

常见错误

错误原因解决
TypeORM 和 Prisma 混用导致冲突两个 ORM 各自维护连接池同一项目只选一个 ORM
ORM 连接字符串格式错误不同数据库驱动格式不同PostgreSQL: postgres://user:pass@host:5432/db;MySQL: mysql://...
开发时频繁报"连接已关闭"连接池耗尽或超时调整 max(连接数)和 idleTimeoutMillis,开发环境用单连接

NestJS 深度学习体系