{T}

ORM对象关系映射详解

ORM对象关系映射详解

学习目标:理解 ORM 的概念、优缺点、主流库对比,掌握 ORM 在 NestJS 中的应用。


一、ORM 的定义

1.1 ORM 是什么?

ORM(Object-Relational Mapping)即 对象关系映射,是一种编程技术。

code
ORM = Object(对象)+ Relational(关系)+ Mapping(映射)

1.2 ORM 的核心作用

将面向对象的概念与数据库中的概念对应起来

面向对象概念数据库概念说明
类(Class)表(Table)定义一个类 = 创建一张表
对象实例(Instance)记录(Record/Row)创建一个对象 = 插入一条记录
属性(Property)字段(Column/Field)对象属性 = 表字段
方法(Method)SQL 操作CRUD 操作

1.3 为什么需要 ORM?

传统方式的痛点

typescript
//  传统方式:直接写 SQL
import mysql from 'mysql2/promise';

const connection = await mysql.createConnection({
  host: 'localhost',
  user: 'root',
  database: 'test'
});

// 执行 SQL 查询
const [rows] = await connection.execute(
  'SELECT * FROM users WHERE id = ?',
  [1]
);

// 手动提取数据
const user = rows[0];
console.log(user.name); // 需要手动处理

ORM 方式的优势

typescript
//  ORM 方式:面向对象操作
import { User } from './entities/user.entity';

// 直接查询对象
const user = await userRepository.findOne({ where: { id: 1 } });

// 直接使用对象属性
console.log(user.name); // 简单直观

对比优势

  • 不需要手写 SQL
  • 不需要关心数据库差异
  • 直接操作对象,符合 OOP 思想
  • 代码可读性强

二、ORM 在架构中的位置

2.1 ORM 架构图

code
┌─────────────────────────────────────────────────────────┐
│                     应用程序层                           │
│                  (面向对象代码)                         │
└────────────────────┬────────────────────────────────────┘
                     │
                     │ 对象操作
                     │
┌────────────────────▼────────────────────────────────────┐
│                      ORM 层                              │
│              (对象关系映射中间件)                        │
│                                                          │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐              │
│  │ 实体定义  │  │ 关系映射  │  │ SQL 生成  │              │
│  └──────────┘  └──────────┘  └──────────┘              │
└────────────────────┬────────────────────────────────────┘
                     │
                     │ SQL 语句
                     │
┌────────────────────▼────────────────────────────────────┐
│                   数据库驱动层                            │
│         (MySQL、PostgreSQL、MongoDB 等)                │
└─────────────────────────────────────────────────────────┘
                     │
                     │ 数据库协议
                     │
┌────────────────────▼────────────────────────────────────┐
│                     数据库层                              │
│              (存储数据的持久化层)                        │
└─────────────────────────────────────────────────────────┘

2.2 ORM 的核心功能

code
ORM 核心功能:
│
├── 实体映射(Entity Mapping)
│   └── 类 ↔ 表,属性 ↔ 字段
│
├── 关系映射(Relationship Mapping)
│   ├── 一对一(1:1)
│   ├── 一对多(1:N)
│   └── 多对多(M:N)
│
├── 查询构建(Query Builder)
│   ├── 链式调用
│   ├── 条件构造
│   └── SQL 生成
│
├── 数据操作(CRUD)
│   ├── Create(创建)
│   ├── Read(读取)
│   ├── Update(更新)
│   └── Delete(删除)
│
└── 事务管理(Transaction)
    └── 原子性、一致性、隔离性、持久性

三、ORM 的特点(优点)

3.1 方便维护

统一的数据模型定义

typescript
//  数据模型定义在一个地方
@Entity('users')
export class User {
  @PrimaryGeneratedColumn()
  id: number;

  @Column({ length: 100 })
  name: string;

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

  @Column({ default: 0 })
  age: number;
}

修改方便

  • 修改模型 = 修改表结构
  • 不需要手动修改 SQL 语句
  • 自动同步到所有使用的地方

3.2 代码量少,对接多种数据库

一套代码,多种数据库

typescript
//  同一套代码,支持多种数据库
import { DataSource } from 'typeorm';

// MySQL
const mysqlDS = new DataSource({
  type: 'mysql',
  host: 'localhost',
  username: 'root',
  password: 'password',
  database: 'test',
  entities: [User]
});

// PostgreSQL
const pgDS = new DataSource({
  type: 'postgres',
  host: 'localhost',
  username: 'postgres',
  password: 'password',
  database: 'test',
  entities: [User]
});

// SQLite
const sqliteDS = new DataSource({
  type: 'sqlite',
  database: 'test.db',
  entities: [User]
});

// 查询代码完全相同
const user = await dataSource.getRepository(User).findOne({ where: { id: 1 } });

对比传统方式

  • 传统:需要学习不同数据库的 SQL 方言
  • 传统:需要安装不同的数据库驱动
  • ORM:统一 API,自动适配

3.3 工具多,自动化能力强

级联操作示例

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

  @Column()
  name: string;

  // 一对多:一个用户有多篇文章
  @OneToMany(() => Article, article => article.author, {
    cascade: true,        // 级联操作
    onDelete: 'CASCADE'   // 删除用户时自动删除文章
  })
  articles: Article[];
}

@Entity('articles')
export class Article {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  title: string;

  // 多对一:多篇文章属于一个用户
  @ManyToOne(() => User, user => user.articles)
  author: User;
}

//  级联保存
const user = new User();
user.name = '张三';
user.articles = [
  { title: '文章1' },
  { title: '文章2' }
];
await userRepository.save(user); // 自动保存用户和文章

//  级联删除
await userRepository.delete(1); // 自动删除用户的所有文章

事务管理示例

typescript
//  事务操作
await dataSource.transaction(async manager => {
  // 创建用户
  const user = new User();
  user.name = '张三';
  await manager.save(user);

  // 创建订单
  const order = new Order();
  order.userId = user.id;
  order.amount = 100;
  await manager.save(order);

  // 如果任何一步失败,自动回滚
  // if (someCondition) {
  //   throw new Error('回滚事务');
  // }
});

链式查询示例

typescript
//  链式操作,类似 JavaScript 数组方法
const users = await dataSource
  .getRepository(User)
  .createQueryBuilder('user')
  .leftJoinAndSelect('user.articles', 'article')
  .where('user.age > :age', { age: 18 })
  .andWhere('article.status = :status', { status: 'published' })
  .orderBy('user.createdAt', 'DESC')
  .skip(0)
  .take(10)
  .getMany();

四、ORM 的缺点

4.1 性能问题

问题

  • 自动生成的 SQL 不够优化
  • 联合查询性能较低
  • 复杂查询性能不如原生 SQL

示例对比

typescript
//  ORM 生成的 SQL 可能不够优化
const users = await userRepository.find({
  relations: ['articles', 'articles.comments', 'articles.comments.user']
});
// 可能生成 N+1 查询问题

//  原生 SQL 可以优化
const users = await dataSource.query(`
  SELECT u.*, a.*, c.*
  FROM users u
  LEFT JOIN articles a ON u.id = a.author_id
  LEFT JOIN comments c ON a.id = c.article_id
  WHERE u.age > 18
  ORDER BY u.created_at DESC
  LIMIT 10
`);

解决方案

  • 使用 Query Builder 手动优化
  • 使用 eager/lazy loading 策略
  • 复杂查询使用原生 SQL
typescript
//  优化后的 ORM 查询
const users = await dataSource
  .getRepository(User)
  .createQueryBuilder('user')
  .leftJoinAndSelect('user.articles', 'article')
  .leftJoinAndSelect('article.comments', 'comment')
  .where('user.age > :age', { age: 18 })
  .orderBy('user.createdAt', 'DESC')
  .limit(10)
  .getMany();

4.2 学习曲线

问题

  • 需要学习 ORM 的 API
  • 需要理解 ORM 的映射规则
  • 需要学习装饰器等语法

对比

  • 传统 SQL:学习 SQL 语法即可
  • ORM:需要学习 ORM API + 装饰器 + 映射规则

五、主流 ORM 库对比

5.1 NPM 下载量对比

ORM 库周下载量GitHub StarsIssues更新频率
Sequelize~1.5M28.9k844活跃
KNEX~1.2M17.5k2.1k活跃
TypeORM~900k33.2k1.7k活跃
Prisma~800k37.5k少量非常活跃

5.2 主流 ORM 库介绍

5.2.1 Sequelize

特点

  • 最老牌的 Node.js ORM
  • 支持多种数据库
  • 成熟稳定
  • 配置复杂
  • TypeScript 支持较弱

示例

typescript
import { Sequelize, Model, DataTypes } from 'sequelize';

const sequelize = new Sequelize('sqlite::memory:');

class User extends Model {
  declare id: number;
  declare name: string;
}

User.init(
  {
    id: {
      type: DataTypes.INTEGER,
      autoIncrement: true,
      primaryKey: true
    },
    name: {
      type: DataTypes.STRING,
      allowNull: false
    }
  },
  { sequelize, tableName: 'users' }
);

// 查询
const users = await User.findAll();

5.2.2 TypeORM (NestJS 官方推荐)

特点

  • NestJS 官方推荐
  • 完美的 TypeScript 支持
  • 装饰器语法,易读易写
  • 支持多种数据库
  • 性能较好

示例

typescript
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';

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

  @Column({ length: 100 })
  name: string;

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

  @Column({ type: 'decimal', precision: 10, scale: 2 })
  balance: number;

  @Column({ type: 'datetime', default: () => 'CURRENT_TIMESTAMP' })
  createdAt: Date;
}

// 查询
const users = await dataSource.getRepository(User).find();

5.2.3 Prisma (后起之秀)

特点

  • 现代化设计,语法简洁
  • 类型安全,自动生成类型
  • 数据库迁移工具强大
  • 可视化管理工具
  • 商业化运营,长期维护
  • 性能略低于 TypeORM

示例

prisma
// schema.prisma
model User {
  id        Int      @id @default(autoincrement())
  name      String
  email     String   @unique
  balance   Decimal  @db.Decimal(10, 2)
  createdAt DateTime @default(now())
  articles  Article[]
}

model Article {
  id        Int      @id @default(autoincrement())
  title     String
  authorId  Int
  author    User     @relation(fields: [authorId], references: [id])
}
typescript
// 查询
const users = await prisma.user.findMany({
  include: {
    articles: true
  }
});

5.2.4 KNEX

特点

  • 查询构建器,灵活度高
  • 支持多种数据库
  • 性能好
  • 不是完整的 ORM
  • 需要手动定义模型

示例

typescript
import knex from 'knex';

const db = knex({
  client: 'mysql2',
  connection: {
    host: 'localhost',
    user: 'root',
    password: 'password',
    database: 'test'
  }
});

// 查询
const users = await db('users')
  .select('*')
  .where('age', '>', 18)
  .orderBy('created_at', 'desc')
  .limit(10);

5.3 TypeORM vs Prisma 详细对比

维度TypeORMPrisma
性能性能更好性能稍逊
易用性装饰器语法语法更简洁
类型安全TypeScript 原生自动生成类型
迁移工具基础迁移强大的迁移工具
可视化工具需要第三方Prisma Studio
社区活跃度社区驱动商业化运营
NestJS 集成官方推荐社区支持
学习曲线装饰器概念简单易懂

TypeORM 示例

typescript
//  TypeORM:装饰器语法
@Entity('users')
export class User {
  @PrimaryGeneratedColumn()
  id: number;

  @Column({ length: 100 })
  name: string;

  @OneToMany(() => Article, article => article.author)
  articles: Article[];
}

// 查询
const user = await dataSource.getRepository(User).findOne({
  where: { id: 1 },
  relations: ['articles']
});

Prisma 示例

prisma
//  Prisma:声明式语法
model User {
  id       Int       @id @default(autoincrement())
  name     String    @db.VarChar(100)
  articles Article[]
}

// 查询
const user = await prisma.user.findUnique({
  where: { id: 1 },
  include: { articles: true }
});

六、ORM 性能对比

6.1 性能测试结果

来源:GitHub 仓库 orm-bench

6.1.1 吞吐量(Throughput)

指标:每秒插入的数据量(越高越好)

code
插入性能对比(ops/sec):
│
├── Raw SQL          ████████████████████ 15,000
├── TypeORM          ████████████████ 12,000
├── Prisma           ██████████████ 10,000
└── Sequelize        ████████████ 9,000

6.1.2 延迟(Latency)

指标:查询延迟时间(越低越好)

code
查询延迟对比(ms):
│
├── Raw SQL          ██ 5ms
├── TypeORM          ████ 12ms
├── Prisma           ██████ 18ms
└── Sequelize        ████████ 25ms

6.2 性能结论

ORM性能排名特点
Raw SQL性能最好,但开发效率低
TypeORM性能较好,NestJS 推荐
Prisma性能稍逊,但易用性最好
Sequelize性能一般,但最成熟

七、ORM 选型建议

7.1 选型决策树

code
ORM 选型决策:
│
├── 使用 NestJS?
│   ├── 是 → TypeORM(官方推荐,完美集成)
│   └── 否 → 继续
│
├── 需要最佳性能?
│   ├── 是 → TypeORM 或 原生 SQL
│   └── 否 → 继续
│
├── 需要最佳易用性?
│   ├── 是 → Prisma(现代化设计)
│   └── 否 → 继续
│
├── 需要强大的迁移工具?
│   ├── 是 → Prisma(数据库迁移神器)
│   └── 否 → TypeORM
│
└── 项目类型?
    ├── 新项目 → Prisma 或 TypeORM
    ├── 旧项目迁移 → TypeORM(兼容性好)
    └── 小型项目 → Prisma(快速开发)

7.2 不同场景推荐

场景推荐 ORM理由
NestJS 项目TypeORM官方推荐,完美集成
新项目(快速开发)Prisma易用性好,迁移工具强大
性能要求高TypeORM 或 原生 SQL性能最优
需要可视化工具PrismaPrisma Studio
旧项目迁移TypeORM兼容性好
小型项目Prisma快速开发

7.3 NestJS 项目推荐

推荐使用 TypeORM

typescript
// 安装依赖
npm install @nestjs/typeorm typeorm mysql2

// app.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';

@Module({
  imports: [
    TypeOrmModule.forRoot({
      type: 'mysql',
      host: 'localhost',
      port: 3306,
      username: 'root',
      password: 'password',
      database: 'test',
      entities: [User],
      synchronize: true, // 开发环境自动同步表结构
    }),
    TypeOrmModule.forFeature([User]),
  ],
})
export class AppModule {}

// user.service.ts
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { User } from './user.entity';

@Injectable()
export class UserService {
  constructor(
    @InjectRepository(User)
    private userRepository: Repository<User>,
  ) {}

  async findAll(): Promise<User[]> {
    return this.userRepository.find();
  }

  async findOne(id: number): Promise<User> {
    return this.userRepository.findOne({ where: { id } });
  }

  async create(user: User): Promise<User> {
    return this.userRepository.save(user);
  }
}

八、ORM 最佳实践

8.1 实体定义规范

typescript
//  推荐的实体定义
@Entity('users')
export class User {
  // 主键
  @PrimaryGeneratedColumn()
  id: number;

  // 基本字段
  @Column({ length: 100, comment: '用户名' })
  name: string;

  @Column({ unique: true, comment: '邮箱' })
  email: string;

  // 枚举字段
  @Column({
    type: 'enum',
    enum: ['user', 'vip', 'admin'],
    default: 'user',
    comment: '用户类型'
  })
  type: string;

  // 默认值字段
  @Column({ default: 0, comment: '状态:0-正常,1-禁用' })
  status: number;

  // 时间字段
  @CreateDateColumn({ comment: '创建时间' })
  createdAt: Date;

  @UpdateDateColumn({ comment: '更新时间' })
  updatedAt: Date;

  // 关系字段
  @OneToMany(() => Article, article => article.author)
  articles: Article[];
}

8.2 查询优化

typescript
//  避免 N+1 查询问题
const users = await userRepository.find();
for (const user of users) {
  const articles = await articleRepository.find({ where: { authorId: user.id } });
}

//  使用关联查询
const users = await userRepository.find({
  relations: ['articles']
});

//  使用 Query Builder
const users = await dataSource
  .getRepository(User)
  .createQueryBuilder('user')
  .leftJoinAndSelect('user.articles', 'article')
  .getMany();

8.3 事务处理

typescript
//  使用事务
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository, DataSource } from 'typeorm';

@Injectable()
export class OrderService {
  constructor(
    private dataSource: DataSource,
    @InjectRepository(Order)
    private orderRepository: Repository<Order>,
  ) {}

  async createOrder(orderData: CreateOrderDto) {
    // 使用事务
    return this.dataSource.transaction(async manager => {
      // 创建订单
      const order = manager.create(Order, orderData);
      await manager.save(order);

      // 扣减库存
      const product = await manager.findOne(Product, { where: { id: orderData.productId } });
      product.stock -= orderData.quantity;
      await manager.save(product);

      // 创建支付记录
      const payment = manager.create(Payment, { orderId: order.id, amount: order.totalAmount });
      await manager.save(payment);

      return order;
    });
  }
}

8.4 数据库迁移

bash
# 生成迁移文件
npm run typeorm migration:generate -- -n AddUserTable

# 运行迁移
npm run typeorm migration:run

# 回滚迁移
npm run typeorm migration:revert
typescript
// 迁移文件示例
import { MigrationInterface, QueryRunner, Table } from 'typeorm';

export class AddUserTable1700000000000 implements MigrationInterface {
  public async up(queryRunner: QueryRunner): Promise<void> {
    await queryRunner.createTable(
      new Table({
        name: 'users',
        columns: [
          {
            name: 'id',
            type: 'int',
            isPrimary: true,
            isGenerated: true,
            generationStrategy: 'increment',
          },
          {
            name: 'name',
            type: 'varchar',
            length: '100',
          },
          {
            name: 'email',
            type: 'varchar',
            isUnique: true,
          },
          {
            name: 'created_at',
            type: 'datetime',
            default: 'CURRENT_TIMESTAMP',
          },
        ],
      }),
    );
  }

  public async down(queryRunner: QueryRunner): Promise<void> {
    await queryRunner.dropTable('users');
  }
}

九、常见问题与解决方案

9.1 N+1 查询问题

问题:查询主表数据后,循环查询关联表数据,导致性能问题。

解决方案

typescript
//  错误做法
const users = await userRepository.find();
for (const user of users) {
  user.articles = await articleRepository.find({ where: { authorId: user.id } });
}

//  正确做法 1:使用 relations
const users = await userRepository.find({
  relations: ['articles']
});

//  正确做法 2:使用 Query Builder
const users = await dataSource
  .getRepository(User)
  .createQueryBuilder('user')
  .leftJoinAndSelect('user.articles', 'article')
  .getMany();

9.2 性能优化问题

问题:ORM 查询性能不如原生 SQL。

解决方案

typescript
//  方案 1:使用 Query Builder 手动优化
const users = await dataSource
  .getRepository(User)
  .createQueryBuilder('user')
  .select(['user.id', 'user.name']) // 只查询需要的字段
  .where('user.age > :age', { age: 18 })
  .limit(10)
  .getMany();

//  方案 2:复杂查询使用原生 SQL
const users = await dataSource.query(`
  SELECT id, name, COUNT(*) as article_count
  FROM users u
  LEFT JOIN articles a ON u.id = a.author_id
  GROUP BY u.id
  ORDER BY article_count DESC
  LIMIT 10
`);

//  方案 3:使用索引
@Entity('users')
@Index(['email', 'status']) // 复合索引
export class User {
  @Column()
  email: string;

  @Column()
  status: number;
}

9.3 数据库切换问题

问题:需要切换数据库类型。

解决方案

typescript
//  ORM 方式:只改配置,代码不变
const dataSource = new DataSource({
  type: 'mysql', // 改为 'postgres'、'sqlite' 等
  host: 'localhost',
  username: 'root',
  password: 'password',
  database: 'test',
  entities: [User, Article],
});

//  传统 SQL:需要修改所有 SQL 语句

9.4 复杂查询问题

问题:ORM 难以实现复杂查询。

解决方案

typescript
//  使用 Query Builder
const result = await dataSource
  .getRepository(User)
  .createQueryBuilder('user')
  .leftJoinAndSelect('user.articles', 'article')
  .leftJoinAndSelect('article.comments', 'comment')
  .where('user.age > :age', { age: 18 })
  .andWhere('article.status = :status', { status: 'published' })
  .groupBy('user.id')
  .having('COUNT(article.id) > :count', { count: 5 })
  .orderBy('user.createdAt', 'DESC')
  .limit(10)
  .getMany();

//  或使用原生 SQL
const result = await dataSource.query(`
  SELECT u.*, COUNT(a.id) as article_count
  FROM users u
  LEFT JOIN articles a ON u.id = a.author_id AND a.status = 'published'
  WHERE u.age > 18
  GROUP BY u.id
  HAVING article_count > 5
  ORDER BY u.created_at DESC
  LIMIT 10
`);

十、ORM 学习路径

10.1 学习阶段规划

code
ORM 学习路径:
│
├── 第一阶段:理解概念(1-2 天)
│   ├── 理解 ORM 的定义和作用
│   ├── 理解 ORM 的优缺点
│   └── 对比不同 ORM 库
│
├── 第二阶段:实践练习(1 周)
│   ├── 学习 TypeORM 基础
│   ├── 实体定义
│   ├── CRUD 操作
│   └── 关系映射
│
└── 第三阶段:深入应用(持续)
    ├── 查询优化
    ├── 事务处理
    ├── 数据库迁移
    └── 性能调优

10.2 学习资源推荐

官方文档

视频教程

  • 慕课网:搜索 "TypeORM" 或 "Prisma"
  • B站:搜索 "NestJS ORM"

实战项目


十一、学习要点总结

11.1 核心概念速记

code
ORM 核心概念:
│
├── 定义
│   └── Object-Relational Mapping(对象关系映射)
│
├── 作用
│   └── 类 ↔ 表,对象 ↔ 记录,属性 ↔ 字段
│
├── 优点
│   ├── 方便维护
│   ├── 代码量少
│   └── 工具多,自动化强
│
├── 缺点
│   ├── 性能问题
│   └── 学习曲线
│
└── 选型
    ├── NestJS → TypeORM(官方推荐)
    ├── 新项目 → Prisma(易用性最好)
    └── 性能要求高 → TypeORM 或 原生 SQL

11.2 重点知识清单

知识点重要程度掌握程度
ORM 的定义和作用未掌握 / 已掌握
ORM 的优缺点未掌握 / 已掌握
TypeORM vs Prisma未掌握 / 已掌握
实体定义未掌握 / 已掌握
CRUD 操作未掌握 / 已掌握
关系映射未掌握 / 已掌握
查询优化未掌握 / 已掌握
事务处理未掌握 / 已掌握

11.3 课后思考题

  1. 什么是 ORM?它的核心作用是什么?
  2. ORM 的优缺点分别是什么?
  3. TypeORM 和 Prisma 有什么区别?应该如何选择?
  4. 如何解决 ORM 的 N+1 查询问题?
  5. 在 NestJS 项目中,如何集成 TypeORM?

十二、扩展阅读

12.1 相关技术栈

code
ORM 相关技术栈:
│
├── 数据库
│   ├── MySQL
│   ├── PostgreSQL
│   ├── MongoDB
│   └── SQLite
│
├── ORM 库
│   ├── TypeORM
│   ├── Prisma
│   ├── Sequelize
│   └── Mongoose(MongoDB)
│
└── 工具
    ├── 数据库迁移
    ├── 可视化管理
    └── 性能监控

12.2 进阶学习方向

  1. 深入 TypeORM

    • Query Builder 高级用法
    • 自定义仓储
    • 订阅者和监听器
  2. 深入 Prisma

    • Prisma Schema 高级用法
    • Prisma Client 优化
    • Prisma Migrate 进阶
  3. 性能优化

    • 查询优化
    • 索引优化
    • 缓存策略
  4. 数据库设计

    • 范式设计
    • 反范式设计
    • 分库分表

参考资料


上一章19-数据库详细设计与实战

下一章21-TypeORM 集成与实体定义