TypeORM 企业级应用实用教程
TypeORM 企业级应用实用教程
一、概述
TypeORM 是 Node.js 生态中广泛使用的ORM(对象关系映射)框架,支持 TypeScript 全类型安全,提供丰富的数据库交互能力。其核心优势在于平衡开发效率与性能,支持多种数据库(MySQL、PostgreSQL、MongoDB 等),并与 Nest.js、Midway 等企业级框架深度集成,是构建复杂业务系统的理想选择。
二、核心概念
2.1 实体(Entity)
定义:映射数据库表的类,通过装饰器声明表结构与字段属性。
示例:
// entity/User.ts
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn } from 'typeorm';
@Entity() // 默认映射表名:user(可通过 @Entity('custom_table_name') 自定义)
export class User {
@PrimaryGeneratedColumn() // 自增主键(INT 类型)
id: number;
@Column({ length: 100 }) // VARCHAR(100),非空
name: string;
@Column({ unique: true }) // 唯一约束(避免重复邮箱)
email: string;
@Column({ select: false }) // 查询时默认不返回(如密码等敏感字段)
password: string;
@Column({ default: false }) // 默认值
isActive: boolean;
@CreateDateColumn() // 自动记录创建时间(DATETIME)
createTime: Date;
@UpdateDateColumn() // 自动记录更新时间(DATETIME)
updateTime: Date;
}
2.2 数据源(DataSource)
定义:数据库连接的核心配置,封装连接参数、实体映射、迁移等配置。
作用:初始化数据库连接,是与数据库交互的入口。
示例:
// data-source.ts
import { DataSource } from 'typeorm';
import { User } from './entity/User';
export const AppDataSource = new DataSource({
type: 'mysql', // 数据库类型(mysql/postgres/sqlite等)
host: process.env.DB_HOST || 'localhost', // 从环境变量读取配置
port: parseInt(process.env.DB_PORT || '3306'),
username: process.env.DB_USER || 'root',
password: process.env.DB_PASSWORD || 'password',
database: process.env.DB_NAME || 'enterprise_db',
entities: [User], // 注册实体
migrations: ['src/migrations/**/*.ts'], // 迁移文件路径
synchronize: process.env.NODE_ENV === 'development', // 开发环境自动同步表结构(生产禁用!)
logging: true, // 日志输出(开发环境开启,生产可关闭)
});
2.3 仓库(Repository)
定义:实体的“数据访问层”,封装 CRUD 操作,通过getRepository(Entity)获取。
核心 API:find()/findOne()/save()/remove()/update()等。
示例:
// service/user.service.ts
import { AppDataSource } from '../data-source';
import { User } from '../entity/User';
// 获取 User 实体的仓库
const userRepository = AppDataSource.getRepository(User);
// 示例:查询用户
async function getUserById(id: number): Promise<User | null> {
return await userRepository.findOne({ where: { id } });
}
// 示例:创建用户(含密码哈希处理)
async function createUser(userData: Partial<User>): Promise<User> {
const user = userRepository.create(userData); // 实例化实体
// 密码哈希(企业级需使用 bcrypt 等工具)
user.password = await bcrypt.hash(user.password, 10);
return await userRepository.save(user); // 保存到数据库
}
2.4 关系(Relations)
TypeORM 支持四种核心关系类型,通过装饰器定义实体间关联:
|
关系类型 |
装饰器 |
场景 |
示例 |
|
一对一 |
@OneToOne+@JoinColumn |
用户-个人资料(1:1) |
@OneToOne(() => Profile) |
|
一对多/多对一 |
@OneToMany+@ManyToOne |
文章-评论(1:N)/ 评论-文章(N:1) |
@OneToMany(() => Comment, comment => comment.post) |
|
多对多 |
@ManyToMany+@JoinTable |
学生-课程(N:N) |
@ManyToMany(() => Course) |
三、企业级最佳实践
3.1 项目结构
推荐按“功能/模块”划分目录,清晰分离实体、仓库、服务、迁移等:
src/
├── entity/ # 实体定义(User.ts, Post.ts...)
├── repository/ # 自定义仓库(扩展基础 Repository)
├── service/ # 业务逻辑层(调用 Repository)
├── migrations/ # 数据库迁移文件
├── data-source.ts # 数据源配置
└── app.ts # 应用入口
3.2 依赖注入集成
在 Nest.js/Midway 等框架中,通过依赖注入获取仓库,避免硬编码:
// Nest.js 示例(user.service.ts)
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { User } from './entity/User';
@Injectable()
export class UserService {
// 注入 User 仓库
constructor(
@InjectRepository(User)
private userRepository: Repository<User>,
) {}
async findAll(): Promise<User[]> {
return this.userRepository.find(); // 直接使用注入的仓库
}
}
3.3 配置管理
核心原则:避免硬编码数据库配置,使用环境变量/配置文件区分环境(开发/测试/生产)。
示例:环境变量 + 配置文件
// config/database.ts
import { DataSourceOptions } from 'typeorm';
import dotenv from 'dotenv';
dotenv.config(); // 加载 .env 文件
export const databaseConfig: DataSourceOptions = {
type: 'postgres',
host: process.env.DB_HOST,
port: parseInt(process.env.DB_PORT || '5432'),
username: process.env.DB_USER,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME,
entities: ['dist/entity/**/*.js'], // 生产环境使用编译后的 JS 文件
migrations: ['dist/migrations/**/*.js'],
synchronize: false, // 生产环境禁用自动同步!
ssl: process.env.NODE_ENV === 'production' ? { rejectUnauthorized: false } : false, // 生产环境启用 SSL
};
3.4 数据库 Schema 管理:迁移(Migration)
核心警告:生产环境禁止使用synchronize: true(可能导致数据丢失),必须通过迁移文件管理表结构变更。
迁移工作流:
1. 创建迁移:
npx typeorm-ts-node-commonjs migration:generate src/migrations/CreateUserTable -d src/data-source.ts
(自动根据实体生成迁移文件)
2. 执行迁移:
npx typeorm-ts-node-commonjs migration:run -d src/data-source.ts
3. 回滚迁移:
npx typeorm-ts-node-commonjs migration:revert -d src/data-source.ts
四、性能与安全优化
4.1 性能优化
(1)索引策略
为高频查询字段添加索引,提升查询效率:
import { Entity, Column, Index } from 'typeorm';
@Entity()
export class User {
@Column()
@Index() // 单字段索引
email: string;
@Index(['name', 'email']) // 联合索引(适用于多字段查询)
@Column()
name: string;
}
(2)避免 N+1 查询问题
使用relations显式加载关联数据,而非依赖自动加载:
// 优化前(N+1 问题:查询用户后,每条用户记录单独查询关联的 profile)
const users = await userRepository.find();
users.forEach(user => console.log(user.profile)); // 触发 N 次额外查询
// 优化后(一次查询加载所有关联数据)
const users = await userRepository.find({
relations: ['profile'], // 显式加载关联
});
(3)QueryBuilder 精确控制 SQL
复杂查询使用QueryBuilder,避免生成冗余 SQL:
// 示例:查询活跃用户并按创建时间排序
const activeUsers = await userRepository
.createQueryBuilder('user')
.where('user.isActive = :isActive', { isActive: true })
.orderBy('user.createTime', 'DESC')
.limit(10)
.getMany();
4.2 安全实践
(1)敏感字段处理
密码等敏感字段默认不返回,查询时显式指定字段:
// 实体定义(设置 select: false)
@Column({ select: false })
password: string;
// 查询时如需返回(如登录验证):
const user = await userRepository
.createQueryBuilder('user')
.select(['user.id', 'user.email', 'user.password']) // 显式指定字段
.where('user.email = :email', { email })
.getOne();
(2)参数化查询防 SQL 注入
TypeORM 所有查询方法默认支持参数化查询,避免直接拼接 SQL:
// 安全(参数化查询)
const users = await userRepository.find({
where: { email: emailParam }, // 自动参数化
});
// 危险(禁止!)
const users = await userRepository.query(`SELECT * FROM user WHERE email = '${emailParam}'`);
五、进阶技巧
5.1 订阅者(Subscriber)
监听实体生命周期事件(如afterInsert、beforeUpdate),实现审计日志、自动字段更新等:
import { EventSubscriber, EntitySubscriberInterface, InsertEvent } from 'typeorm';
import { User } from './entity/User';
@EventSubscriber()
export class UserSubscriber implements EntitySubscriberInterface<User> {
listenTo() {
return User; // 订阅 User 实体
}
// 插入前自动设置创建人
beforeInsert(event: InsertEvent<User>) {
event.entity.createdBy = 'system'; // 自动填充创建人字段
}
}
5.2 事务处理
使用事务确保多步操作的原子性(全部成功或全部失败):
// 使用 EntityManager 事务
async function transferFunds(fromId: number, toId: number, amount: number) {
return await AppDataSource.transaction(async manager => {
const fromUser = await manager.findOne(User, { where: { id: fromId } });
const toUser = await manager.findOne(User, { where: { id: toId } });
fromUser.balance -= amount;
toUser.balance += amount;
await manager.save(fromUser);
await manager.save(toUser); // 任一保存失败,事务回滚
});
}
5.3 延迟加载(Lazy Relations)
对非高频访问的关联数据使用延迟加载(按需加载):
import { OneToOne } from 'typeorm';
import { Profile } from './Profile';
@Entity()
export class User {
@OneToOne(() => Profile, { lazy: true }) // 延迟加载
profile: Promise<Profile>; // 返回 Promise,需 await 获取
}
// 使用时按需加载
const user = await userRepository.findOne({ where: { id } });
const profile = await user.profile; // 触发关联查询
六、常见挑战与应对
6.1 复杂查询优化
挑战:多表关联、聚合查询性能差。
应对:
- 优先使用QueryBuilder手写优化 SQL;
- 添加合适的联合索引;
- 拆分复杂查询为多个简单查询,在应用层聚合结果。
6.2 生产环境配置管理
挑战:多环境(开发/测试/生产)配置切换繁琐。
应对:
- 使用dotenv+config模块管理环境变量;
- 生产环境配置通过 CI/CD 注入(如 Kubernetes ConfigMap、云服务环境变量)。
6.3 学习曲线陡峭
挑战:关系定义、迁移、QueryBuilder 等概念复杂。
应对:
- 从官方文档的“快速入门”开始,逐步实践实体、仓库、关系基础功能;
- 复杂场景参考社区示例(如 Nest.js + TypeORM 集成教程)。
七、总结
TypeORM 是企业级 Node.js 应用的强大 ORM 工具,核心价值在于类型安全、丰富的数据库交互能力、框架集成友好。在实际应用中,需重点关注:
- 生产环境禁用synchronize,使用迁移管理表结构;
- 优化查询性能:索引、显式关联加载、QueryBuilder;
- 安全实践:敏感字段处理、参数化查询、密码哈希;
- 工程化配置:环境变量管理、依赖注入集成。
通过本文档的最佳实践,可构建健壮、高效、易维护的企业级数据库交互层。
浙公网安备 33010602011771号