从 Sequelize 迁移到 TypeORM:数据源、实体定义、字段选项与索引的完整对照指南
2026/9/10 22:39:31 网站建设 项目流程

从 Sequelize 迁移到 TypeORM:数据源、实体定义、字段选项与索引的完整对照指南

【免费下载链接】typeormTypeScript & JavaScript ORM for Node.js — supports PostgreSQL, MySQL, MariaDB, SQLite, SQL Server, Oracle, and more.项目地址: https://gitcode.com/GitHub_Trending/ty/typeorm

本文是一份面向 Sequelize 存量项目的 TypeORM 迁移实战指南,核心围绕“同一个需求在 Sequelize 与 TypeORM 中各自的写法”展开:从数据源初始化、表结构同步,到实体模型与各种字段约束(可空、默认值、唯一、自增、主键、时间戳列),再到模型的新增/更新与多列索引创建,均给出可直接对照、可运行的代码。读完本文,你能够把一段现有的 Sequelize 模型代码逐行翻译成 TypeORM 的实体定义,并了解底层装饰器与DataSource的实现机制,做到“知其然也知其所以然”。

本文内容以 迁移指南 为骨架,代码示例、结论均可在此文档与仓库源码中找到依据。

一、初始化数据源:从new Sequelize(...)new DataSource({...})

在 Sequelize 中,数据源(连接配置)通过new Sequelize(database, username, password, options)创建,随后调用authenticate()验证连接是否成功:

const sequelize = new Sequelize("database", "username", "password", { host: "localhost", dialect: "mysql", }) sequelize .authenticate() .then(() => { console.log("Data Source has been initialized successfully.") }) .catch((err) => { console.error("Error during Data Source initialization:", err) })

TypeORM 中使用DataSource承载相同的职责,但它把连接参数统一收进一个选项对象,并用type字段取代 Sequelize 的dialect

import { DataSource } from "typeorm" const dataSource = new DataSource({ type: "mysql", host: "localhost", username: "username", password: "password", }) dataSource .initialize() .then(() => { console.log("Data Source has been initialized successfully.") }) .catch((err) => { console.error("Error during Data Source initialization:", err) })

两者最直观的差异是构造签名:

维度SequelizeTypeORM
构造方式位置参数(database, user, password)+ 选项全部使用选项对象{ type, host, username, password, database }
数据库类型字段dialect: "mysql"type: "mysql"
连接验证/初始化authenticate()initialize()
可用范围需要传入各模型全局导出后可在任意模块使用

几点迁移要点:

  • 推荐在单独文件中创建DataSource实例并export,这样应用各处都能复用同一连接。官方 DataSource 指南 同样建议“把AppDataSource通过export全局暴露”,并且一个应用可以按需创建多个数据源(例如 MySQL 一个、Postgres 一个)。
  • 不要忘记type是可选的数据库平台集合(mysqlpostgresmariadbsqlitemssqloraclemongodb等),DataSource接受的DataSourceOptions会随type不同而提供不同字段。更多参数含义见 DataSourceOptions。
  • 从源码看,initialize()是真正的“建连 + 初始化元数据”入口(DataSource.ts),并且在满足条件时还会触发自动建表(见下文同步机制)。

二、表结构同步:从逐模型sync()synchronize: true

Sequelize 中要为每个模型单独同步表结构:

Project.sync({ force: true }) Task.sync({ force: true })

{ force: true }表示“先 DROP 再 CREATE”,容易造成数据丢失。TypeORM 则把“是否自动同步”做成了数据源级开关,只需在选项里加一个布尔值:

const dataSource = new DataSource({ type: "mysql", host: "localhost", username: "username", password: "password", synchronize: true, })

此时 TypeORM 会在启动时根据你注册的所有实体(entities)自动比对并创建/更新表结构。源码层面,initialize()在建立连接后会检查this.options.synchronize,为真则调用内部synchronize()方法(DataSource.ts)。

生产环境提示synchronize: true适合开发期快速迭代;在真实生产环境里,更稳妥的做法是把它关闭,改用显式的 Migrations 机制 通过迁移文件管理结构变更,可审计、可回滚。Sequelize 生态同样有独立迁移工具,TypeORM 则把迁移作为一等公民内置。

三、定义模型:从sequelize.define到实体类(Entity)

Sequelize 用sequelize.define("tableName", attributes)工厂函数定义模型:

module.exports = function (sequelize, DataTypes) { const Project = sequelize.define("project", { title: DataTypes.STRING, description: DataTypes.TEXT, }) return Project }
module.exports = function (sequelize, DataTypes) { const Task = sequelize.define("task", { title: DataTypes.STRING, description: DataTypes.TEXT, deadline: DataTypes.DATE, }) return Task }

在 TypeORM 中,模型被命名为Entity(实体)——一个普通 TypeScript 类配合装饰器声明式地描述表结构:

import { Entity, PrimaryGeneratedColumn, Column } from "typeorm" @Entity() export class Project { @PrimaryGeneratedColumn() id: number @Column() title: string @Column() description: string }
import { Entity, PrimaryGeneratedColumn, Column } from "typeorm" @Entity() export class Task { @PrimaryGeneratedColumn() id: number @Column() title: string @Column("text") description: string @Column() deadline: Date }

迁移建议与约定:

  • 一个实体类放一个文件,这是社区强烈推荐的实践,也便于通过 glob 批量注册实体。
  • @Entity()标记类映射到数据库表;默认表名取类名(Projectproject),也可显式指定@Entity("my_table_name")
  • @PrimaryGeneratedColumn()声明自增主键;@Column()声明普通列。
  • 类型映射:DataTypes.STRING通常对应默认字符串列;DataTypes.TEXT需要显式写成@Column("text")(示例中正是如此);DataTypes.DATE对应实体里的Date属性。
  • TypeORM 允许把类直接当作数据库模型使用,并“以声明式方式说明模型的哪一部分会成为数据库表列”。同时,TypeScript 的类型系统会为这些类带来类型提示(type hinting)等额外收益——这是相比纯 JS 的 Sequelize 模型的一大优势。

值得留意的是:实体中每个被@Column()标记的属性最终都会映射为表的一列;列的数据库类型既可以通过@Column第一个参数显式给出(如@Column("text")),也可以不写、让 TypeORM 依据属性反射元数据自动推断。实体必须拥有主键列(关系型场景),否则会报错。字段类型、主键列、特殊列的完整展开可参考 Entities 文档。

四、其他模型设置:Sequelize 字段选项 →@Column选项

这是迁移中最琐碎也最常碰到的部分。下面逐条给出“Sequelize 写法 → TypeORM 写法”的对照。

4.1 可空 + 默认值

Sequelize:

flag: { type: Sequelize.BOOLEAN, allowNull: true, defaultValue: true },

TypeORM 使用nullabledefault两个列选项:

@Column({ nullable: true, default: true }) flag: boolean;

4.2 数据库当前时间作为默认值

Sequelize:

flag: { type: Sequelize.DATE, defaultValue: Sequelize.NOW }

TypeORM:

@Column({ default: () => "NOW()" }) myDate: Date;

这里用函数形式default: () => "NOW()"让默认值落到数据库侧的NOW()(生成数据库层面的DEFAULT),而不是在 JS 侧先计算一个固定时间。

4.3 唯一约束

Sequelize:

someUnique: { type: Sequelize.STRING, unique: true },

TypeORM:

@Column({ unique: true }) someUnique: string;

从源码看,@Column({ unique: true })会被拆成两步处理:一方面注册普通列元数据,另一方面向uniques元数据集合登记一条唯一约束(Column.ts),最终在数据库中生成唯一约束或唯一索引。

4.4 数据库列名与属性名不一致

Sequelize 用field指定物理列名:

fieldWithUnderscores: { type: Sequelize.STRING, field: "field_with_underscores" },

TypeORM 中对应的是name

@Column({ name: "field_with_underscores" }) fieldWithUnderscores: string;

默认情况下 TypeORM 会用属性名生成列名;需要别名时用name覆盖。

4.5 自增列

Sequelize:

incrementMe: { type: Sequelize.INTEGER, autoIncrement: true },

TypeORM:

@Column() @Generated() incrementMe: number;

@Generated()用于标记“插入实体时自动生成值”的非主键列。其默认生成策略为increment(源码 Generated.ts 中strategy: "increment" | "uuid" | "rowid" = "increment");另外也支持@Generated("uuid")。需要留意的是,部分数据库只允许一张表存在一个 increment 列,或要求自增列必须是主键,若遇阻可改用@PrimaryGeneratedColumn()

4.6 手动主键

Sequelize:

identifier: { type: Sequelize.STRING, primaryKey: true },

TypeORM:

@Column({ primary: true }) identifier: string;

@Column({ primary: true })等价于使用@PrimaryColumn()。若希望该主键自增或生成 UUID,则应使用@PrimaryGeneratedColumn()/@PrimaryGeneratedColumn("uuid")

4.7createDate/updateDate时间戳列

Sequelize 通常需要手动配置createdAt/updatedAt。TypeORM 为此提供了专用的时间戳列装饰器,不必每次手写默认值:

@CreateDateColumn(); createDate: Date; @UpdateDateColumn(); updateDate: Date;

@CreateDateColumn()会在对象首次插入时写入创建时间且此后不再变动,实现上以mode: "createDate"注册列元数据(CreateDateColumn.ts);@UpdateDateColumn()则在每次执行save/upsert(命中更新分支)时自动刷新为当前时间。列名可由你自由命名。

除上述对照外,@Column还支持更丰富的选项,例如lengthvarchar(150))、commentprecision/scaleselect(查询默认是否隐藏该列)、transformer(读写时类型转换)等,详见 Entity columns 章节。上述多数选项为关系型数据库专用,在 MongoDB 驱动下不可用。

4.8 小结对照表

Sequelize 属性选项TypeORM 写法说明
allowNull: truenullable: true列是否允许 NULL
defaultValue: valuedefault: value列默认值
defaultValue: Sequelize.NOWdefault: () => "NOW()"数据库侧当前时间
unique: trueunique: true生成唯一约束
field: "db_name"name: "db_name"指定物理列名
autoIncrement: true@Column()+@Generated()自增值生成
primaryKey: trueprimary: true(或@PrimaryColumn()声明主键
createdAt/updatedAt@CreateDateColumn()/@UpdateDateColumn()自动时间戳

五、使用模型:新增、保存、加载与属性访问

5.1 创建并保存新模型

Sequelize 通过Model.create(object)一行完成“创建 + 落库”:

const employee = await Employee.create({ name: "John Doe", title: "senior engineer", })

TypeORM 提供了多种等价途径,你可以按团队偏好选择Data MapperActive Record两种模式(这也是 Sequelize 迁移到 TypeORM 后最需要适应的编程风格差异)。

Data Mapper 风格(默认):实体只描述属性,通过仓库(Repository)操作数据库。

const employee = new Employee() // 也可以在构造函数中传参 employee.name = "John Doe" employee.title = "senior engineer" await dataSource.getRepository(Employee).save(employee)

Active Record 风格:实体继承BaseEntity,直接在模型上提供savefind等静态与实例方法。

const employee = Employee.create({ name: "John Doe", title: "senior engineer" }) await employee.save()

BaseEntity.createRepository.create的静态等价物,BaseEntity上还提供preloadsaveremovefindOneBy等一系列对应方法(见 BaseEntity.ts 中的静态方法实现)。两种模式各有取舍:Data Mapper 在大型应用中更利于维护(职责分离),Active Record 在小型项目中更简洁直接。完整对比见 Active Record vs Data Mapper。

5.2 预加载并替换已存在实体

如果想从数据库加载一条已存在记录并仅替换其中部分属性,两种风格都能用preload

const employee = await Employee.preload({ id: 1, name: "John Doe" })

注意它的语义:给定对象必须携带主键id),TypeORM 先按该主键查出完整实体,再用传入对象中的属性做覆盖;若对应 id 不存在,则返回undefined。其底层经由Repository.preload转发给EntityManager.preload(Repository.ts)。相比 Sequelize 先findByPk再手动赋值的惯用法,preload一步到位。

5.3 访问属性

Sequelize 中属性需要经过实例的get()方法:

console.log(employee.get("name"))

TypeORM 的实体就是普通类实例,直接访问属性即可:

console.log(employee.name)

这带来的直接收益是:IDE 自动补全 + 编译期类型检查成为常态,字符串键拼错的问题可以在编译阶段被拦截。

Repository/BaseEntity上其余常用 API(findfindOneByfindAndCountsaveremovesoftDeleteupdateupsert、分页选项等)的完整签名可查阅 Repository API。

六、创建索引:从indexes配置到@Index装饰器

Sequelize 在define的第三个参数里集中配置索引数组:

sequelize.define( "user", {}, { indexes: [ { unique: true, fields: ["firstName", "lastName"], }, ], }, )

TypeORM 把索引以装饰器形式直接附着在实体/列上。上面的“复合唯一索引”等价写法是把@Index放在类上,并传入参与索引的列名数组:

@Entity() @Index(["firstName", "lastName"], { unique: true }) export class User {}

@Index在 Index.ts 中提供多种重载形态,按需选用:

  • 单列索引:直接标注在列属性上,如@Index()加在@Column()上;
  • 带名索引@Index("name-idx")
  • 列级唯一索引@Index({ unique: true })
  • 实体级多列索引@Index(["col1", "col2"]),可加{ unique: true }变成复合唯一索引。

单一列唯一约束更常见的是直接写@Column({ unique: true })。空间索引(spatial: true)、synchronize关闭后配合迁移脚本手工建索引等更多场景,参见 Indexes 完整文档。

七、迁移路线总结与进一步阅读

把上述各节串起来,一条典型的 Sequelize → TypeORM 迁移路径是:

  1. 建数据源:把new Sequelize(db, user, pass, { host, dialect })换成导出的new DataSource({ type, host, username, password, database, entities })
  2. 小步同步:开发期打开synchronize: true验证结构,生产切到 Migrations;
  3. 改写模型:把每个sequelize.define(...)文件改写成“一个实体类一个文件”,并用@Entity/@Column系列装饰器逐字段翻译类型与约束;
  4. 替换读写:把Model.createmodel.get("x")Model.update等调用点改造成 Repository / EntityManager / BaseEntity 风格;
  5. 平移索引:把模型第三参的indexes配置翻译成列级或实体级@Index

每一步都可以回到对应官方文档做深度补充:数据源与连接生命周期见 DataSource 指南 与 DataSourceOptions 参数说明;实体与列的细节见 Entities 文档;仓储层完整 API 见 Repository API;编程范式选型见 Active Record vs Data Mapper。对于仓库中的真实实现,可继续阅读 DataSource.ts、Column.ts、Generated.ts、BaseEntity.ts 等源码文件,验证本文所述机制。

【免费下载链接】typeormTypeScript & JavaScript ORM for Node.js — supports PostgreSQL, MySQL, MariaDB, SQLite, SQL Server, Oracle, and more.项目地址: https://gitcode.com/GitHub_Trending/ty/typeorm

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询