TypeORM——基于 TypeScript/JavaScript 的对象关系映射(ORM)框架

TypeORM 是一个基于 TypeScript/JavaScript 的对象关系映射(ORM)框架 ,支持 Node.js、浏览器等多平台,可对接 MySQL、PostgreSQL、SQLite、SQL Server 等主流数据库。其核心设计理念是"让开发者用面向对象的方式操作数据库",通过装饰器或实体类定义数据模型,自动生成 SQL 并管理数据库交互。以下基于 TypeORM v0.3.x(最新稳定版)官方文档,详细介绍其用法。

https://typeorm.io/

https://github.com/typeorm/typeorm

一、核心概念与设计模式

TypeORM 支持两种编程模式:

  • Active Record :实体类直接包含 CRUD 方法(如 save()remove()),适合简单场景。
  • Data Mapper :实体类仅定义数据结构,通过 RepositoryEntityManager 执行操作,适合复杂业务逻辑(推荐)。

二、安装与环境准备

1. 基础依赖安装
bash 复制代码
# 安装 TypeORM 核心库
npm install typeorm reflect-metadata --save

# 安装数据库驱动(根据使用的数据库选择)
# MySQL/MariaDB
npm install mysql2 --save
# PostgreSQL
npm install pg --save
# SQLite
npm install sqlite3 --save
# SQL Server
npm install tedious --save
2. TypeScript 配置(关键!)

tsconfig.json 中启用装饰器支持:

json 复制代码
{
  "compilerOptions": {
    "experimentalDecorators": true,  // 启用装饰器
    "emitDecoratorMetadata": true,    // 允许反射元数据(TypeORM 依赖)
    "target": "ES2020",
    "module": "CommonJS"
  }
}



三、核心用法详解

1. 定义实体(Entity)

实体是映射到数据库表的类,通过装饰器 @Entity() 标记,字段用 @Column() 定义。

示例:用户实体(User)

typescript 复制代码
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn } from "typeorm";

@Entity("users") // 映射到数据库表名(默认类名小写复数,此处显式指定)
export class User {
  @PrimaryGeneratedColumn() // 自增主键(等价于 AUTO_INCREMENT)
  id: number;

  @Column({ length: 50, unique: true }) // 字符串列,长度50,唯一约束
  username: string;

  @Column({ select: false }) // 查询时默认不返回该字段(如密码)
  password: string;

  @Column({ type: "int", default: 0 }) // 整数列,默认值0
  age: number;

  @Column({ type: "boolean", default: true }) // 布尔列,默认值true
  isActive: boolean;

  @Column({ type: "timestamp", nullable: true }) // 时间戳列,可为空
  lastLoginAt: Date;

  @CreateDateColumn() // 自动记录创建时间(无需手动赋值)
  createdAt: Date;

  @UpdateDateColumn() // 自动记录更新时间(更新时自动刷新)
  updatedAt: Date;
}

常用列装饰器参数

  • type:数据库类型(如 "varchar""int""boolean""timestamp"),默认根据属性类型推断。
  • length:字符串长度(如 { length: 255 })。
  • nullable:是否允许 NULL(默认 false)。
  • default:默认值(如 { default: "active" })。
  • unique:是否唯一约束(如 { unique: true })。
2. 定义关系(Relationships)

TypeORM 支持 一对一(@OneToOne)、一对多(@OneToMany)、多对多(@ManyToMany) 关系,通过外键或中间表关联。

示例:用户(User)与文章(Post)的一对多关系

typescript 复制代码
// post.entity.ts
import { Entity, PrimaryGeneratedColumn, Column, ManyToOne } from "typeorm";
import { User } from "./user.entity";

@Entity("posts")
export class Post {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  title: string;

  @Column("text")
  content: string;

  // 多对一:多个 Post 属于一个 User(外键 userId)
  @ManyToOne(() => User, (user) => user.posts, { 
    onDelete: "CASCADE" // 用户删除时,级联删除其所有文章
  })
  user: User; // 关联的用户对象(查询时可加载)

  @Column() // 显式存储外键(可选,TypeORM 会自动维护)
  userId: number;
}

// 更新 User 实体,添加反向关联(一对多)
// user.entity.ts
import { Post } from "./post.entity";

@Entity("users")
export class User {
  // ... 其他字段 ...
  @OneToMany(() => Post, (post) => post.user) // 一个 User 有多个 Post
  posts: Post[]; // 用户的文章列表(查询时可加载)
}

关系核心参数

  • target:关联的实体类(如 () => User)。
  • inverseSide:反向关联的属性(如 (user) => user.posts,表示 User 类中关联 Post 的字段)。
  • onDelete:级联删除策略("CASCADE"/"SET NULL"/"RESTRICT")。
3. 创建数据库连接

TypeORM v0.3.x 推荐使用 DataSource 类管理连接(替代旧版 createConnection)。

示例:创建 MySQL 连接

typescript 复制代码
import { DataSource } from "typeorm";
import { User } from "./entity/User";
import { Post } from "./entity/Post";

const AppDataSource = new DataSource({
  type: "mysql", // 数据库类型
  host: "localhost",
  port: 3306,
  username: "root",
  password: "password",
  database: "test_db",
  synchronize: true, // 开发环境自动同步实体到数据库(生产环境禁用!)
  logging: ["error", "warn"], // 日志级别
  entities: [User, Post], // 注册的实体类
  migrations: [], // 迁移文件(见下文)
  subscribers: [], // 订阅者(事件监听)
});

// 初始化连接
AppDataSource.initialize()
  .then(() => console.log("数据库连接成功"))
  .catch((error) => console.error("连接失败", error));

关键参数说明

  • synchronize: true:自动根据实体定义创建/更新表结构(仅开发环境使用,生产环境需用迁移)。
  • entities:数组形式注册实体类(也可通过 glob 路径加载,如 [__dirname + "/entity/*.ts"])。
4. CRUD 操作(基于 Repository)

通过 DataSource.getRepository(Entity) 获取实体的 Repository,执行 CRUD。

示例:User 实体的 CRUD

typescript 复制代码
// 假设已初始化 AppDataSource
const userRepository = AppDataSource.getRepository(User);

// 1. 创建(新增)用户
async function createUser() {
  const user = new User();
  user.username = "john_doe";
  user.password = "hashed_password"; // 实际需加密
  user.age = 30;
  await userRepository.save(user); // 保存到数据库
  console.log("用户创建成功,ID:", user.id);
}

// 2. 查询用户
async function getUsers() {
  // 查询所有用户(排除 password 字段,因 @Column({ select: false }))
  const allUsers = await userRepository.find();
  
  // 条件查询:年龄 > 25 且激活状态
  const activeUsers = await userRepository.find({
    where: { age: MoreThan(25), isActive: true }, // MoreThan 需导入:import { MoreThan } from "typeorm"
    order: { createdAt: "DESC" }, // 按创建时间倒序
    take: 10, // 限制返回10条
  });
  
  // 查询单个用户(按 ID)
  const user = await userRepository.findOneBy({ id: 1 });
  
  // 关联查询:加载用户的文章(posts 字段)
  const userWithPosts = await userRepository.findOne({
    where: { id: 1 },
    relations: ["posts"], // 加载关联的 posts 字段
  });
}

// 3. 更新用户
async function updateUser(id: number) {
  const user = await userRepository.findOneBy({ id });
  if (user) {
    user.age = 31;
    user.isActive = false;
    await userRepository.save(user); // 保存更新
    // 或直接更新部分字段(无需先查询)
    await userRepository.update(id, { age: 31, isActive: false });
  }
}

// 4. 删除用户
async function deleteUser(id: number) {
  // 软删除(推荐,需配合 @DeleteDateColumn)
  // 硬删除(物理删除)
  await userRepository.delete(id); 
}

常用查询方法

  • find(options):查询多条,optionswhere/order/take/skip/relations
  • findOne(options):查询单条,options.where 可传对象或表达式。
  • findOneBy(where):简化版单条查询(仅 where 条件)。
  • save(entity):新增或更新(若实体有 id 则更新)。
  • update(where, partialEntity):批量更新符合条件的记录。
  • delete(where):删除符合条件的记录。
5. 高级特性
(1)迁移(Migrations)

迁移用于版本控制数据库结构 ,替代 synchronize: true(生产环境必备)。

步骤

  1. 生成迁移文件:

    bash 复制代码
    npx typeorm-ts-node-commonjs migration:generate -d src/migrations/Datasource src/migrations/InitUsersTable

    生成的文件位于 src/migrations,包含 up(执行变更)和 down(回滚变更)方法。

  2. 编辑迁移文件(示例:创建 users 表):

    typescript 复制代码
    import { MigrationInterface, QueryRunner } from "typeorm";
    
    export class InitUsersTable1700000000000 implements MigrationInterface {
      public async up(queryRunner: QueryRunner): Promise<void> {
        await queryRunner.query(`
          CREATE TABLE users (
            id INT PRIMARY KEY AUTO_INCREMENT,
            username VARCHAR(50) UNIQUE NOT NULL,
            password VARCHAR(255) NOT NULL,
            age INT DEFAULT 0,
            is_active BOOLEAN DEFAULT TRUE,
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
            updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
          )
        `);
      }
    
      public async down(queryRunner: QueryRunner): Promise<void> {
        await queryRunner.query(`DROP TABLE users`);
      }
    }
  3. 执行迁移:

    bash 复制代码
    npx typeorm-ts-node-commonjs migration:run -d src/migrations/Datasource
  4. 回滚迁移:

    bash 复制代码
    npx typeorm-ts-node-commonjs migration:revert -d src/migrations/Datasource
(2)事务(Transactions)

通过 EntityManagerRepository.manager 开启事务,确保多步操作的原子性。

示例:转账事务(用户A扣钱,用户B加钱)

typescript 复制代码
async function transferMoney(fromId: number, toId: number, amount: number) {
  return AppDataSource.manager.transaction(async (transactionalEntityManager) => {
    // 1. 查询用户A(加锁防止并发问题)
    const fromUser = await transactionalEntityManager.findOneBy(User, { id: fromId }, { lock: { mode: "pessimistic_write" } });
    if (!fromUser || fromUser.balance < amount) throw new Error("余额不足");

    // 2. 查询用户B
    const toUser = await transactionalEntityManager.findOneBy(User, { id: toId });
    if (!toUser) throw new Error("目标用户不存在");

    // 3. 执行转账
    fromUser.balance -= amount;
    toUser.balance += amount;
    await transactionalEntityManager.save([fromUser, toUser]);
  });
}
(3)查询构建器(QueryBuilder)

用于复杂查询(如联表、子查询、聚合函数),支持链式调用。

示例:查询"用户及其最近3篇文章"

typescript 复制代码
const userWithRecentPosts = await userRepository
  .createQueryBuilder("user") // 别名 "user"
  .leftJoinAndSelect("user.posts", "post") // 联表并加载 posts,别名为 "post"
  .where("user.id = :id", { id: 1 }) // 条件:用户ID=1
  .orderBy("post.createdAt", "DESC") // 按文章创建时间倒序
  .take(3) // 取3篇
  .getOne(); // 获取单条结果

常用方法

  • createQueryBuilder(alias):创建构建器,指定主表别名。
  • leftJoinAndSelect(relation, alias):左联并加载关联表。
  • where(condition, parameters):添加 WHERE 条件。
  • andWhere(condition):追加 AND 条件。
  • orderBy(field, direction):排序。
  • getOne()/getMany():执行查询并返回单条/多条结果。
(4)软删除(Soft Delete)

通过 @DeleteDateColumn() 标记删除时间字段,删除时仅更新该字段(而非物理删除)。

示例

typescript 复制代码
@Entity()
export class User {
  // ... 其他字段 ...
  @DeleteDateColumn({ name: "deleted_at" }) // 软删除标记(默认列名 deletedAt)
  deletedAt: Date | null;
}

// 使用:软删除用户
await userRepository.softDelete(1); // 设置 deletedAt 为当前时间

// 查询时排除已软删除的记录(默认行为)
const activeUsers = await userRepository.find();

// 包含已软删除的记录
const allUsers = await userRepository.find({ withDeleted: true });

四、完整示例:用户-文章系统

1. 实体定义
typescript 复制代码
// user.entity.ts
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn, OneToMany, DeleteDateColumn } from "typeorm";
import { Post } from "./post.entity";

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

  @Column({ length: 50, unique: true })
  username: string;

  @Column({ select: false })
  password: string;

  @Column({ type: "int", default: 0 })
  age: number;

  @Column({ type: "boolean", default: true })
  isActive: boolean;

  @CreateDateColumn()
  createdAt: Date;

  @UpdateDateColumn()
  updatedAt: Date;

  @DeleteDateColumn()
  deletedAt: Date;

  @OneToMany(() => Post, (post) => post.user)
  posts: Post[];
}

// post.entity.ts
import { Entity, PrimaryGeneratedColumn, Column, ManyToOne } from "typeorm";
import { User } from "./user.entity";

@Entity("posts")
export class Post {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  title: string;

  @Column("text")
  content: string;

  @ManyToOne(() => User, (user) => user.posts, { onDelete: "CASCADE" })
  user: User;

  @Column()
  userId: number;
}
2. 数据库连接与 CRUD
typescript 复制代码
import "reflect-metadata";
import { DataSource } from "typeorm";
import { User } from "./entity/user.entity";
import { Post } from "./entity/post.entity";

const AppDataSource = new DataSource({
  type: "mysql",
  host: "localhost",
  port: 3306,
  username: "root",
  password: "password",
  database: "test_db",
  synchronize: true, // 开发环境临时使用
  entities: [User, Post],
});

AppDataSource.initialize().then(async () => {
  const userRepo = AppDataSource.getRepository(User);
  const postRepo = AppDataSource.getRepository(Post);

  // 创建用户
  const user = new User();
  user.username = "alice";
  user.password = "pass123";
  user.age = 28;
  await userRepo.save(user);

  // 创建文章
  const post = new Post();
  post.title = "Hello TypeORM";
  post.content = "TypeORM is awesome!";
  post.user = user; // 关联用户
  post.userId = user.id;
  await postRepo.save(post);

  // 查询用户及其文章
  const result = await userRepo.findOne({
    where: { id: user.id },
    relations: ["posts"],
  });
  console.log(result); // { id: 1, username: 'alice', ..., posts: [{ id: 1, title: 'Hello TypeORM', ... }] }
});

五、注意事项

  1. 生产环境禁用 synchronize: true:改用迁移(Migrations)管理表结构变更。
  2. 性能优化 :避免 N+1 查询(通过 relationsjoin 预加载关联数据)。
  3. 事务使用:涉及多表修改时必须用事务保证一致性。
  4. 类型安全:优先使用 TypeScript 类型定义实体,减少运行时错误。
  5. 官方文档:始终参考 https://typeorm.io/(最新版)获取细节。

通过以上内容,可掌握 TypeORM 的核心用法。实际开发中需结合业务场景选择合适的模式(Active Record/Data Mapper),并充分利用其关系映射、迁移、事务等特性构建健壮的数据层。


相关推荐
程序员小寒2 小时前
JavaScript设计模式(一):单例模式实现与应用
javascript·单例模式·设计模式
Dxy12393102162 小时前
JS如何把数据添加到列表中
前端·javascript·vue.js
御形封灵2 小时前
基于canvas的路网编辑交互
开发语言·javascript·交互
m0_502724952 小时前
Arco design vue 阻止弹窗关闭
javascript·vue.js·arco design
蜡台2 小时前
Uniapp 实现 二手车价格评估 功能
前端·javascript·uni-app·估值·汽车抵押·二手车评估
Yan-英杰2 小时前
TypeScript+React 全栈生态实战:从架构选型到工程落地,告别开发踩坑
javascript·学习·typescript
海天鹰2 小时前
JSZip库读取ePub电子书目录
javascript
比特森林探险记2 小时前
Element Plus 实战指南
前端·javascript
FlyWIHTSKY2 小时前
Vue3 单文件中不同的组件
前端·javascript·vue.js