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 Stars | Issues | 更新频率 |
|---|---|---|---|---|
| Sequelize | ~1.5M | 28.9k | 844 | 活跃 |
| KNEX | ~1.2M | 17.5k | 2.1k | 活跃 |
| TypeORM | ~900k | 33.2k | 1.7k | 活跃 |
| Prisma | ~800k | 37.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 详细对比
| 维度 | TypeORM | Prisma |
|---|---|---|
| 性能 | 性能更好 | 性能稍逊 |
| 易用性 | 装饰器语法 | 语法更简洁 |
| 类型安全 | 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,0006.1.2 延迟(Latency)
指标:查询延迟时间(越低越好)
code
查询延迟对比(ms):
│
├── Raw SQL ██ 5ms
├── TypeORM ████ 12ms
├── Prisma ██████ 18ms
└── Sequelize ████████ 25ms6.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 | 性能最优 |
| 需要可视化工具 | Prisma | Prisma 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:reverttypescript
// 迁移文件示例
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:https://typeorm.io/
- Prisma:https://www.prisma.io/
- Sequelize:https://sequelize.org/
视频教程:
- 慕课网:搜索 "TypeORM" 或 "Prisma"
- B站:搜索 "NestJS ORM"
实战项目:
十一、学习要点总结
11.1 核心概念速记
code
ORM 核心概念:
│
├── 定义
│ └── Object-Relational Mapping(对象关系映射)
│
├── 作用
│ └── 类 ↔ 表,对象 ↔ 记录,属性 ↔ 字段
│
├── 优点
│ ├── 方便维护
│ ├── 代码量少
│ └── 工具多,自动化强
│
├── 缺点
│ ├── 性能问题
│ └── 学习曲线
│
└── 选型
├── NestJS → TypeORM(官方推荐)
├── 新项目 → Prisma(易用性最好)
└── 性能要求高 → TypeORM 或 原生 SQL11.2 重点知识清单
| 知识点 | 重要程度 | 掌握程度 |
|---|---|---|
| ORM 的定义和作用 | 未掌握 / 已掌握 | |
| ORM 的优缺点 | 未掌握 / 已掌握 | |
| TypeORM vs Prisma | 未掌握 / 已掌握 | |
| 实体定义 | 未掌握 / 已掌握 | |
| CRUD 操作 | 未掌握 / 已掌握 | |
| 关系映射 | 未掌握 / 已掌握 | |
| 查询优化 | 未掌握 / 已掌握 | |
| 事务处理 | 未掌握 / 已掌握 |
11.3 课后思考题
- 什么是 ORM?它的核心作用是什么?
- ORM 的优缺点分别是什么?
- TypeORM 和 Prisma 有什么区别?应该如何选择?
- 如何解决 ORM 的 N+1 查询问题?
- 在 NestJS 项目中,如何集成 TypeORM?
十二、扩展阅读
12.1 相关技术栈
code
ORM 相关技术栈:
│
├── 数据库
│ ├── MySQL
│ ├── PostgreSQL
│ ├── MongoDB
│ └── SQLite
│
├── ORM 库
│ ├── TypeORM
│ ├── Prisma
│ ├── Sequelize
│ └── Mongoose(MongoDB)
│
└── 工具
├── 数据库迁移
├── 可视化管理
└── 性能监控12.2 进阶学习方向
-
深入 TypeORM
- Query Builder 高级用法
- 自定义仓储
- 订阅者和监听器
-
深入 Prisma
- Prisma Schema 高级用法
- Prisma Client 优化
- Prisma Migrate 进阶
-
性能优化
- 查询优化
- 索引优化
- 缓存策略
-
数据库设计
- 范式设计
- 反范式设计
- 分库分表
参考资料
上一章:19-数据库详细设计与实战