TypeORM 数据库操作:装饰器驱动与迁移

FreeGuideOnline 最新 2026-06-15

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 中启用 experimentalDecoratorsemitDecoratorMetadata

{
  "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 应用,从模型定义到数据操作再到模式演进一气呵成。