TypeORM 数据库操作:装饰器驱动与迁移
TypeORM 快速入门
TypeORM 是一个支持 TypeScript 和 JavaScript 的 ORM 框架,它通过装饰器(Decorators)让你以面向对象的方式定义数据库模型,并提供了强大的迁移工具来管理数据库模式的演进。本教程将带你从零掌握 TypeORM 的核心操作:实体定义、数据源配置、CRUD 以及数据库迁移。
安装与初始化
在 Node.js 项目中安装必要依赖:
npm install typeorm reflect-metadata mysql2 # 以 MySQL 为例
npm install @types/node --save-dev
reflect-metadata 用于支持装饰器元数据。确保 tsconfig.json 中启用 experimentalDecorators 和 emitDecoratorMetadata:
{
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"strictPropertyInitialization": false
}
}
数据源配置
TypeORM 的数据源(DataSource)负责连接数据库。推荐使用 ormconfig.json 或直接在代码中配置。以下是在代码中创建数据源的基本示例:
import { DataSource } from "typeorm";
import { User } from "./entity/User";
export const AppDataSource = new DataSource({
type: "mysql",
host: "localhost",
port: 3306,
username: "root",
password: "yourpassword",
database: "mydb",
synchronize: false, // 生产环境务必设为 false,使用迁移管理
logging: true,
entities: [User], // 可手动导入或 glob 路径
migrations: ["src/migration/**/*.ts"],
subscribers: [],
});
在入口文件初始化连接:
import "reflect-metadata";
import { AppDataSource } from "./data-source";
AppDataSource.initialize()
.then(() => console.log("Data Source has been initialized!"))
.catch((err) => console.error("Error during initialization:", err));
实体定义 – 装饰器驱动
实体(Entity)代表数据库中的一张表。通过装饰器将类的属性映射到列、关系等。
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn } from "typeorm";
@Entity("users") // 表名可自定义
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column({ type: "varchar", length: 100, unique: true })
username: string;
@Column({ select: false }) // 查询时默认不返回密码
password: string;
@Column({ type: "tinyint", default: 1 })
isActive: boolean;
@CreateDateColumn()
createdAt: Date;
@UpdateDateColumn()
updatedAt: Date;
}
常用列装饰器选项:
type:数据库列类型length:对于字符串类型,设定长度nullable:是否可为空default:默认值unique:唯一约束select: false:查询时排除该列,需显式 addSelect 才会返回
关系映射
TypeORM 支持 @OneToOne, @OneToMany, @ManyToOne, @ManyToMany 来定义表关系。以用户和帖子为例:
// User.ts
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;
@OneToMany(() => Post, (post) => post.author)
posts: Post[];
}
// Post.ts
@Entity()
export class Post {
@PrimaryGeneratedColumn()
id: number;
@Column()
title: string;
@ManyToOne(() => User, (user) => user.posts)
author: User;
}
只在拥有外键的一方(Post)使用 @ManyToOne,另一方使用 @OneToMany 反向引用。这样能自动生成外键列 authorId。
仓储与 CRUD 操作
通过数据源获取实体的仓储(Repository)即可进行增删改查。
import { AppDataSource } from "./data-source";
import { User } from "./entity/User";
async function crudExample() {
// 获取 User 仓储
const userRepo = AppDataSource.getRepository(User);
// 1. 创建并保存新用户
const newUser = userRepo.create({
username: "john_doe",
password: "hashed_password",
});
await userRepo.save(newUser);
// 2. 查询所有用户
const allUsers = await userRepo.find();
// 3. 按条件查询(带选择特定字段)
const user = await userRepo.findOne({
where: { username: "john_doe" },
select: ["id", "username", "isActive"],
});
// 4. 更新
if (user) {
user.isActive = false;
await userRepo.save(user);
}
// 5. 删除
await userRepo.delete({ username: "john_doe" });
}
也可以使用 QueryBuilder 进行复杂查询:
const users = await userRepo
.createQueryBuilder("user")
.leftJoinAndSelect("user.posts", "post")
.where("user.isActive = :isActive", { isActive: true })
.orderBy("user.createdAt", "DESC")
.getMany();
数据库迁移(Migrations)
synchronize: true 仅适合开发阶段,生产环境必须使用迁移来追踪模式变更。迁移文件由 TypeORM CLI 或通过代码生成。
1. 配置 CLI
在 package.json 中添加脚本,并创建 ormconfig.ts(或使用前面定义好的 DataSource 导出):
"scripts": {
"typeorm": "typeorm-ts-node-commonjs",
"migration:generate": "npm run typeorm -- migration:generate src/migration/MigrationName -d ./src/data-source.ts",
"migration:run": "npm run typeorm -- migration:run -d ./src/data-source.ts",
"migration:revert": "npm run typeorm -- migration:revert -d ./src/data-source.ts"
}
2. 生成迁移
修改实体(如新增列)后,执行:
npm run migration:generate -- src/migration/AddEmailToUser
TypeORM 会对比实体与当前数据库模式,生成对应的 SQL 文件。
3. 运行与回滚
npm run migration:run # 执行尚未运行的迁移
npm run migration:revert # 回滚最后一次迁移
迁移文件结构示例:
import { MigrationInterface, QueryRunner } from "typeorm";
export class AddEmailToUser1620000000000 implements MigrationInterface {
name = 'AddEmailToUser1620000000000';
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE \`users\` ADD \`email\` varchar(255) NULL`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE \`users\` DROP COLUMN \`email\``);
}
}
务必确保 down 方法能回滚 up 所做的修改,保持迁移的原子性。
常见最佳实践
- 环境分离:使用不同
.env文件配置数据源,通过process.env注入。 - 始终使用迁移:关闭
synchronize,所有 DDL 变更都通过迁移脚本管理。 - 惰性关系:对于大对象,可使用
lazy: true或显式relations选项控制加载。 - 索引与性能:在常用查询字段上加
@Index()。 - 事务处理:使用
dataSource.transaction()包裹多个写操作。
总结
通过装饰器定义的实体让你能快速将 TypeScript 类映射为数据库表,结合迁移机制可实现安全、可追溯的数据库版本控制。掌握本教程的内容后,你已经能够构建稳健的 TypeORM 应用,从模型定义到数据操作再到模式演进一气呵成。