migrations
迁移
迁移工作原理
一旦您进入生产环境,您就需要将模型更改同步到数据库中。通常,当您在数据库中有数据时,在生产环境中使用 synchronize: true
进行模式同步是不安全的。这就是迁移发挥作用的地方。
迁移只是一个包含 SQL 查询的单个文件,用于更新数据库架构并将新更改应用于现有数据库。
假设您已经有一个数据库和一个帖子实体:
import { Entity, Column, PrimaryGeneratedColumn } from "typeorm"
@Entity()
export class Post {
@PrimaryGeneratedColumn()
id: number
@Column()
title: string
@Column()
text: string
}
并且您的实体在生产环境中运行了数月而没有任何更改。您的数据库中有成千上万个帖子。
现在您需要进行一个新的发布,并将 title
更名为 name
。您将怎么做?
您需要创建一个新的迁移,其中包含以下 SQL 查询(postgres 方言):
ALTER TABLE "post" ALTER COLUMN "title" RENAME TO "name";
一旦运行此 SQL 查询,您的数据库架构就准备好与新的代码库一起使用了。TypeORM 提供了一个地方,您可以编写此类 SQL 查询并在需要时运行它们。这个地方被称为 "迁移"。
创建新迁移
前提条件:安装 CLI
在创建新迁移之前,您需要正确设置数据源选项:
{
type: "mysql",
host: "localhost",
port: 3306,
username: "test",
password: "test",
database: "test",
entities: [/*...*/],
migrations: [/*...*/],
migrationsTableName: "custom_migration_table",
}
在这里,我们设置了两个选项:
"migrationsTableName": "migrations"
- 仅在迁移表名与"migrations"
不同时才指定此选项。"migrations": [/*...*/]
- 要由 TypeORM 加载的迁移列表。
一旦您设置了连接选项,您可以使用 CLI 创建一个新的迁移:
typeorm migration:create ./path-to-migrations-dir/PostRefactoring
在这里,PostRefactoring
是迁移的名称 - 您可以指定任何您想要的名称。运行命令后,您可以在 "migration" 目录中看到一个新生成的文件,名称为 {TIMESTAMP}-PostRefactoring.ts
,其中 {TIMESTAMP}
是生成迁移时的当前时间戳。现在,您可以打开文件并在其中添加迁移的 SQL 查询。
您应该在迁移中看到以下内容:
import { MigrationInterface, QueryRunner } from "typeorm"
export class PostRefactoringTIMESTAMP implements MigrationInterface {
async up(queryRunner: QueryRunner): Promise<void> {}
async down(queryRunner: QueryRunner): Promise<void> {}
}
在这里,有两个方法您必须使用您的迁移代码填充:up
和 down
。up
方法必须包含执行迁移所需的代码。down
方法必须撤销 up
所做的更改。down
方法用于还原最后一个迁移。
在 up
和 down
方法内部,您有一个 QueryRunner
对象。所有数据库操作都是使用此对象执行的。了解更多关于 query runner。
让我们看看带有我们 Post
更改的迁移是什么样子:
import { MigrationInterface, QueryRunner } from "typeorm"
export class PostRefactoringTIMESTAMP implements MigrationInterface {
async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE "post" RENAME COLUMN "title" TO "name"`,
)
}
async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE "post" RENAME COLUMN "name" TO "title"`,
) // 撤销 "up" 方法所做的更改
}
}
Running and reverting migrations
一旦您有要在生产环境中运行的迁移,您可以使用以下 CLI 命令来运行它们:
typeorm migration:run -- -d path-to-datasource-config
typeorm migration:create
和 typeorm migration:generate
会创建 .ts
文件,除非您使用 o
标志(更多信息请参见生成迁移)。migration:run
和 migration:revert
命令仅适用于 .js
文件。因此,在运行命令之前,需要对 TypeScript 文件进行编译。 或者,您可以在运行 .ts
迁移文件时使用 ts-node
。
使用 ts-node
的示例:
npx typeorm-ts-node-commonjs migration:run -- -d path-to-datasource-config
ESM 项目中使用 ts-node
的示例:
npx typeorm-ts-node-esm migration:run -- -d path-to-datasource-config
此命令将执行所有待处理的迁移,并按照它们的时间戳顺序依次运行它们。
这意味着您创建的迁移的 up
方法中编写的所有 SQL 查询都将被执行。
这就是全部!现在您的数据库模式已经是最新的。
如果出于某种原因,您想要撤销更改,可以运行:
typeorm migration:revert -- -d path-to-datasource-config
此命令将执行最近执行的迁移的 down
方法。
如果您需要撤销多个迁移,则必须多次调用此命令。
Faking Migrations and Rollbacks
您还可以使用 --fake
标志(缩写为 -f
)来模拟运行迁移。这将将迁移添加到迁移表中,而不运行它。这对于在数据库已经进行了手动更改或在外部运行了迁移(例如由其他工具或应用程序运行)之后仍然希望保持一致的迁移历史记录非常有用。
typeorm migration:run --fake
回滚也可以进行虚拟运行。
typeorm migration:revert --fake
Transaction modes
默认情况下,TypeORM 将在一个包装事务内运行所有迁移。
这对应于 --transaction all
标志。
如果您需要更精细的事务控制,可以使用 --transaction each
标志将每个迁移单独包装在事务中,或使用 --transaction none
标志完全取消将迁移包装在事务中的操作。
除了这些标志之外,还可以通过在 MigrationInterface
上设置 transaction
属性为 true
或 false
来覆盖每个迁移的事务行为。这仅在 each
或 none
事务模式下起作用。
import { MigrationInterface, QueryRunner } from "typeorm"
export class AddIndexTIMESTAMP implements MigrationInterface {
transaction = false
async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`CREATE INDEX CONCURRENTLY post_names_idx ON post(name)`
)
}
async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`DROP INDEX CONCURRENTLY post_names_idx`,
)
}
}
生成迁移
TypeORM能够根据你所做的模式更改自动生成迁移文件。
假设你有一个带有title
列的Post
实体,并且你已经将title
更改为name
。
你可以运行以下命令:
typeorm migration:generate PostRefactoring -d 数据源配置路径
如果遇到任何错误,它会要求你提供迁移名称和数据源的路径。你可以尝试这个选项:
typeorm migration:generate -d <path/to/datasource> path/to/migrations/<migration-name>
它将生成一个名为{TIMESTAMP}-PostRefactoring.ts
的新迁移文件,内容如下:
import { MigrationInterface, QueryRunner } from "typeorm"
export class PostRefactoringTIMESTAMP implements MigrationInterface {
async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE "post" ALTER COLUMN "title" RENAME TO "name"`,
)
}
async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE "post" ALTER COLUMN "name" RENAME TO "title"`,
)
}
}
或者你也可以使用o
(--outputJs
的别名)标志将迁移输出为Javascript文件。这对于仅使用Javascript的项目很有用,其中没有安装TypeScript附加包。这个命令将生成一个新的迁移文件{TIMESTAMP}-PostRefactoring.js
,内容如下:
const { MigrationInterface, QueryRunner } = require("typeorm")
module.exports = class PostRefactoringTIMESTAMP {
async up(queryRunner) {
await queryRunner.query(
`ALTER TABLE "post" ALTER COLUMN "title" RENAME TO "name"`,
)
}
async down(queryRunner) {
await queryRunner.query(
`ALTER TABLE "post" ALTER COLUMN "name" RENAME TO "title"`,
)
}
}
你不需要手动编写查询语句。
生成迁移的经验法则是,在对模型进行任何更改后都要生成迁移。如果要对生成的迁移查询应用多行格式,可以使用p
(--pretty
的别名)标志。
DataSource选项
如果你需要运行/回滚/生成/显示迁移,请使用-d
(--dataSource
的别名)选项,并将你定义DataSource实例的文件路径作为参数传递
typeorm -d <你的数据源路径> migration:{run|revert}
Timestamp选项
如果你需要为迁移名称指定一个时间戳,请使用-t
(--timestamp
的别名)选项,并传递一个时间戳(应为非负数)
typeorm -t <具体时间戳> migration:{create|generate}
你可以使用以下代码获得时间戳:
Date.now()
/* 或者 */ new Date().getTime()
使用迁移API编写迁移
为了使用API更改数据库模式,你可以使用QueryRunner
。
示例:
import {
MigrationInterface, QueryRunner, Table, TableIndex, TableColumn, TableForeignKey
} from "typeorm"
export class QuestionRefactoringTIMESTAMP implements MigrationInterface {
async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.createTable(
new Table({
name: "question",
columns: [
{
name: "id",
type: "int",
isPrimary: true,
},
{
name: "name",
type: "varchar",
},
],
}),
true,
)
await queryRunner.createIndex(
"question",
new TableIndex({
name: "IDX_QUESTION_NAME",
columnNames: ["name"],
}),
)
await queryRunner.createTable(
new Table({
name: "answer",
columns: [
{
name: "id",
type: "int",
isPrimary: true,
},
{
name: "name",
type: "varchar",
},
{
name: "created_at",
type: "timestamp",
default: "now()",
},
],
}),
true,
)
await queryRunner.addColumn(
"answer",
new TableColumn({
name: "questionId",
type: "int",
}),
)
await queryRunner.createForeignKey(
"answer",
new TableForeignKey({
columnNames: ["questionId"],
referencedColumnNames: ["id"],
referencedTableName: "question",
onDelete: "CASCADE",
}),
)
}
async down(queryRunner: QueryRunner): Promise<void> {
const table = await queryRunner.getTable("answer")
const foreignKey = table.foreignKeys.find(
(fk) => fk.columnNames.indexOf("questionId") !== -1,
)
await queryRunner.dropForeignKey("answer", foreignKey)
await queryRunner.dropColumn("answer", "questionId")
await queryRunner.dropTable("answer")
await queryRunner.dropIndex("question", "IDX_QUESTION_NAME")
await queryRunner.dropTable("question")
}
}
getDatabases(): Promise<string[]>
获取所有可用的数据库名称,包括系统数据库。
getSchemas(database?: string): Promise<string[]>
database
- 如果指定了数据库参数,则返回该数据库的模式
获取所有可用的模式名称,包括系统模式。仅适用于 SQL Server 和 PostgreSQL。
getTable(tableName: string): Promise<Table|undefined>
tableName
- 要加载的表名
从数据库中加载指定名称的表。
getTables(tableNames: string[]): Promise<Table[]>
tableNames
- 要加载的表名
从数据库中加载指定名称的表。
hasDatabase(database: string): Promise<boolean>
database
- 要检查的数据库名称
检查是否存在指定名称的数据库。
hasSchema(schema: string): Promise<boolean>
schema
- 要检查的模式名称
检查是否存在指定名称的模式。仅适用于 SQL Server 和 PostgreSQL。
hasTable(table: Table|string): Promise<boolean>
table
- 表对象或名称
检查是否存在指定的表。
hasColumn(table: Table|string, columnName: string): Promise<boolean>
table
- 表对象或名称columnName
- 要检查的列名
检查表中是否存在指定的列。
createDatabase(database: string, ifNotExist?: boolean): Promise<void>
database
- 数据库名称ifNotExist
- 如果为true
,则在数据库已存在时跳过创建;否则,如果数据库已存在则抛出错误
创建新的数据库。
dropDatabase(database: string, ifExist?: boolean): Promise<void>
database
- 数据库名称ifExist
- 如果为true
,则在数据库未找到时跳过删除;否则,如果数据库未找到则抛出错误
删除数据库。
createSchema(schemaPath: string, ifNotExist?: boolean): Promise<void>
schemaPath
- 模式名称。对于 SQL Server,可以接受模式路径(例如 'dbName.schemaName')作为参数。 如果传递了模式路径,则在指定的数据库中创建模式。ifNotExist
- 如果为true
,则在模式已存在时跳过创建;否则,如果模式已存在则抛出错误
创建新的表模式。
dropSchema(schemaPath: string, ifExist?: boolean, isCascade?: boolean): Promise<void>
schemaPath
- 模式名称。对于 SQL Server,可以接受模式路径(例如 'dbName.schemaName')作为参数。 如果传递了模式路径,则在指定的数据库中删除模式。ifExist
- 如果为true
,则在模式未找到时跳过删除;否则,如果模式未找到则抛出错误。isCascade
- 如果为true
,则自动删除包含在模式中的对象(表、函数等)。 仅适用于 PostgreSQL。
删除表模式。
createTable(table: Table, ifNotExist?: boolean, createForeignKeys?: boolean, createIndices?: boolean): Promise<void>
table
- 表对象。ifNotExist
- 如果为true
,则在表已存在时跳过创建;否则,如果表已存在则抛出错误。默认为false
。createForeignKeys
- 指示在创建表时是否创建外键。默认为true
。createIndices
- 指示在创建表时是否创建索引。默认为true
。
创建新表。
dropTable(table: Table|string, ifExist?: boolean, dropForeignKeys?: boolean, dropIndices?: boolean): Promise<void>
table
- 表对象或要删除的表名称。ifExist
- 如果为true
,则在表不存在时跳过删除;否则,如果表不存在则抛出错误。dropForeignKeys
- 指示在删除表时是否删除外键。默认为true
。dropIndices
- 指示在删除表时是否删除索引。默认为true
。
删除表。
renameTable(oldTableOrName: Table|string, newTableName: string): Promise<void>
oldTableOrName
- 要重命名的旧表对象或名称。newTableName
- 新表名称。
重命名表。
addColumn(table: Table|string, column: TableColumn): Promise<void>
table
- 表对象或名称。column
- 新列。
添加新列。
addColumns(table: Table|string, columns: TableColumn[]): Promise<void>
table
- 表对象或名称。columns
- 新列数组。
添加新列。
renameColumn(table: Table|string, oldColumnOrName: TableColumn|string, newColumnOrName: TableColumn|string): Promise<void>
table
- 表对象或名称。oldColumnOrName
- 旧列。可接受 TableColumn 对象或列名称。newColumnOrName
- 新列。可接受 TableColumn 对象或列名称。
重命名列。
changeColumn(table: Table|string, oldColumn: TableColumn|string, newColumn: TableColumn): Promise<void>
table
- 表对象或名称。oldColumn
- 旧列。可接受 TableColumn 对象或列名称。newColumn
- 新列。可接受 TableColumn 对象。
修改表中的列。
changeColumns(table: Table|string, changedColumns: { oldColumn: TableColumn, newColumn: TableColumn }[]): Promise<void>
table
- 表对象或名称。changedColumns
- 更改的列数组。oldColumn
- 旧的 TableColumn 对象。newColumn
- 新的 TableColumn 对象。
修改表中的列。
dropColumn(table: Table|string, column: TableColumn|string): Promise<void>
table
- 表对象或名称。column
- 要删除的 TableColumn 对象或列名称。
删除表中的列。
dropColumns(table: Table|string, columns: TableColumn[]|string[]): Promise<void>
table
- 表对象或名称。columns
- 要删除的 TableColumn 对象数组或列名称数组。
删除表中的列。
createPrimaryKey(table: Table|string, columnNames: string[]): Promise<void>
table
- 表对象或名称。columnNames
- 主键的列名称数组。
创建新的主键。
updatePrimaryKeys(table: Table|string, columns: TableColumn[]): Promise<void>
table
- 表对象或名称。columns
- 要更新的 TableColumn 对象数组。
更新复合主键。
dropPrimaryKey(table: Table|string): Promise<void>
table
- 表对象或名称。
删除主键。
createUniqueConstraint(table: Table|string, uniqueConstraint: TableUnique): Promise<void>
table
- 表对象或名称。uniqueConstraint
- 要创建的 TableUnique 对象。
创建新的唯一约束。
注意:对于 MySQL 不起作用,因为 MySQL 将唯一约束存储为唯一索引。请改用
createIndex()
方法。
createUniqueConstraints(table: Table|string, uniqueConstraints: TableUnique[]): Promise<void>
table
- 表对象或名称。uniqueConstraints
- 要创建的 TableUnique 对象数组。
创建新的唯一约束。
注意:对于 MySQL 不起作用,因为 MySQL 将唯一约束存储为唯一索引。请改用
createIndices()
方法。
dropUniqueConstraint(table: Table|string, uniqueOrName: TableUnique|string): Promise<void>
table
- 表对象或名称。uniqueOrName
- 要删除的 TableUnique 对象或唯一约束名称。
删除唯一约束。
注意:对于 MySQL 不起作用,因为 MySQL 将唯一约束存储为唯一索引。请改用
dropIndex()
方法。
dropUniqueConstraints(table: Table|string, uniqueConstraints: TableUnique[]): Promise<void>
table
- 表对象或名称。uniqueConstraints
- 要删除的 TableUnique 对象数组。
删除唯一约束。
注意:对于 MySQL 不起作用,因为 MySQL 将唯一约束存储为唯一索引。请改用
dropIndices()
方法。
createCheckConstraint(table: Table|string, checkConstraint: TableCheck): Promise<void>
table
- 表对象或名称。checkConstraint
- TableCheck 对象。
创建新的检查约束。
注意:MySQL 不支持检查约束。
createCheckConstraints(table: Table|string, checkConstraints: TableCheck[]): Promise<void>
table
- 表对象或名称。checkConstraints
- TableCheck 对象数组。
创建新的检查约束。
注意:MySQL 不支持检查约束。
dropCheckConstraint(table: Table|string, checkOrName: TableCheck|string): Promise<void>
table
- 表对象或名称。checkOrName
- 要删除的 TableCheck 对象或检查约束名称。
删除检查约束。
注意:MySQL 不支持检查约束。
dropCheckConstraints(table: Table|string, checkConstraints: TableCheck[]): Promise<void>
table
- 表对象或名称。checkConstraints
- 要删除的 TableCheck 对象数组。
删除检查约束。
注意:MySQL 不支持检查约束。
createForeignKey(table: Table|string, foreignKey: TableForeignKey): Promise<void>
table
- 表对象或名称。foreignKey
- TableForeignKey 对象。
创建新的外键。
createForeignKeys(table: Table|string, foreignKeys: TableForeignKey[]): Promise<void>
table
- 表对象或名称。foreignKeys
- 要创建的 TableForeignKey 对象数组。
创建新的外键。
dropForeignKey(table: Table|string, foreignKeyOrName: TableForeignKey|string): Promise<void>
table
- 表对象或名称。foreignKeyOrName
- 要删除的 TableForeignKey 对象或外键名称。
删除外键。
dropForeignKeys(table: Table|string, foreignKeys: TableForeignKey[]): Promise<void>
table
- 表对象或名称。foreignKeys
- 要删除的 TableForeignKey 对象数组。
删除外键。
createIndex(table: Table|string, index: TableIndex): Promise<void>
table
- 表对象或名称。index
- TableIndex 对象。
创建新的索引。
createIndices(table: Table|string, indices: TableIndex[]): Promise<void>
table
- 表对象或名称。indices
- TableIndex 对象数组。
创建新的索引。
dropIndex(table: Table|string, index: TableIndex|string): Promise<void>
table
- 表对象或名称。index
- TableIndex 对象或索引名称。
删除索引。
dropIndices(table: Table|string, indices: TableIndex[]): Promise<void>
table
- 表对象或名称。indices
- TableIndex 对象数组。
删除索引。
clearTable(tableName: string): Promise<void>
tableName
- 表名称。
清空表中的所有内容。
注意:此操作使用 SQL 的 TRUNCATE 查询,无法在事务中回滚。
enableSqlMemory(): void
启用特殊的查询运行器模式,其中 SQL 查询不会被执行,而是会被存储在查询运行器内的一个特殊变量中。
可以使用 getMemorySql()
方法获取存储的 SQL。
disableSqlMemory(): void
禁用特殊的查询运行器模式,之前存储的 SQL 将被清空。
clearSqlMemory(): void
清空所有存储的 SQL 语句。
getMemorySql(): SqlInMemory
- 返回一个包含
upQueries
和downQueries
SQL 语句数组的SqlInMemory
对象。
获取存储在内存中的 SQL。其中的参数已被替换。
executeMemoryUpSql(): Promise<void>
执行存储的升级 SQL 语句。
executeMemoryDownSql(): Promise<void>
执行存储的降级 SQL 语句。