基于Node使用TypeORM的数据迁移

3,027 阅读4分钟

最近在用Node.js练习写后端,操作数据库的方面自然用到了比较火的一款TypeORM框架,在使用迁移(migration)时,看文档查百度好容易弄出来了(感觉我点了谷歌翻译后的官方文档不够清楚啊-_-||),先记录下来。

迁移(Migration)

迁移就我的理解来说的话,就是通过我们写的代码来修改数据库的解构或者数据(用于不同环境间的数据库解构同步),好处的话:

1. 通过代码编写,不用直接操作数据库;
2. 直接通过命令来操作,简单快捷。

实体

因为TypeORM的表生成是通过对应的实体来生成的,所以得先了解什么是实体。
TypeORM里面,实体其实就是一个Class,也就是一个普通的类,只是这个类需要通过@Entity装饰器来装饰,类里面的每一个属性,对应的都是数据表的每一个字段,例如:

import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';

@Entity({
  name: 'article'
})
class Article {
  @PrimaryGeneratedColumn()
  id: number = 0;
  
  @Column({
    type: 'varchar',
    length: 50,
    comment: '标题'
  })
  title: string = '';
  
  @Column({
    type: 'varchar',
    length: 100,
    comment: '描述'
  })
  desc: string = '';
}

export default Article

这样的一个类,就可以称之为实体(或者叫实体类)。

开始迁移

开始迁移前我们需要定义一个配置文件,这里用的是ts作为配置文件来使用:

import { resolve } from 'path';
import { ConnectionOptions } from 'typeorm';

const dbConfig: ConnectionOptions = {
  type: 'mysql',
  host: 'localhost',
  port: 3306,
  username: 'root',
  password: 'password',
  database: 'myblog',
  entities: [
    'src/model/*.ts'  // 对应的实体类位置
  ],
  migrations: [
    'src/migrations/**/*.ts'  // 对应的迁移文件位置
  ],
  cli: {
    entitiesDir: 'src/model',  // 实体类的目录位置
    migrationsDir: 'src/migrations'  // 迁移文件的目录位置
  }
}

export default dbConfig;

这个配置文件也是typeorm连接数据库所需要的。

了解这些概念后,就可以通过TypeORM开始实现迁移功能,因为使用的是ts,所以我们需要在package.json里面配置几条命令,这些命令可以去官网文档里面查看:

"typeorm:create": "ts-node --transpile-only ./node_modules/typeorm/cli.js --config src/config/db.config.ts migration:create -n",
"typeorm:gen": "ts-node --transpile-only ./node_modules/typeorm/cli.js --config src/config/db.config.ts migration:generate -n",
"typeorm:run": "ts-node --transpile-only ./node_modules/typeorm/cli.js migration:run --config src/config/db.config.ts"

typeorm:create

这个命令的作用就是用于生成一个migration文件,我们需要操作数据库的代码,例如执行命令:

npm run typeorm:create UpdateArticle

就会生成下面这些代码:

import {MigrationInterface, QueryRunner} from "typeorm";

export class UpdateArticle1633872542504 implements MigrationInterface {

    public async up(queryRunner: QueryRunner): Promise<void> {
        await queryRunner.query(`ALTER TABLE \`article\` MODIFY \`desc\` VARCHAR(200)`);
    }

    public async down(queryRunner: QueryRunner): Promise<void> {
        await queryRunner.query(`ALTER TABLE \`article\` MODIFY \`desc\` VARCHAR(100)`);
    }

}

上面的一个类就是通过create命令生成的,这里面主要有两个方法:

  • up:包含迁移所需执行的代码;
  • down:包含迁移恢复的代码。

其实就是:up是我们想要去执行操作数据库的sql语句,如果这个语句有问题,执行不了的话,就需要执行down方法来恢复成原来的样子,避免数据库遭到污染(系统应该会自动执行的)。

typeorm:gen

这个命令是用来生成对应实体类的migration,通过这个migration就可以创建对应的表,例如:

npm run typeorm:gen CreateArticle

会生成一个TIMESTAMP-CreateArticle.ts文件,这个文件就已经包含了创建表的sql了:

import {MigrationInterface, QueryRunner} from "typeorm";

export class CreateAritcle1633872275180 implements MigrationInterface {
    name = 'CreateAritcle1633872275180'

    public async up(queryRunner: QueryRunner): Promise<void> {
        await queryRunner.query(`CREATE TABLE \`article\` (\`id\` int NOT NULL AUTO_INCREMENT, \`title\` varchar(50) NOT NULL COMMENT '标题', \`desc\` varchar(100) NOT NULL COMMENT '描述', \`view\` int NOT NULL COMMENT '浏览数量', \`good\` int NOT NULL COMMENT '点赞数量', \`message\` int NOT NULL COMMENT '留言数量', \`createTime\` varchar(13) NOT NULL COMMENT '创建时间', \`updateTime\` varchar(13) NOT NULL COMMENT '修改时间', \`userId\` int NOT NULL COMMENT '用户ID', PRIMARY KEY (\`id\`)) ENGINE=InnoDB`);
        await queryRunner.query(`ALTER TABLE \`article\` ADD CONSTRAINT \`FK_636f17dadfea1ffb4a412296a28\` FOREIGN KEY (\`userId\`) REFERENCES \`user\`(\`id\`) ON DELETE NO ACTION ON UPDATE NO ACTION`);
    }

    public async down(queryRunner: QueryRunner): Promise<void> {
        await queryRunner.query(`ALTER TABLE \`article\` DROP FOREIGN KEY \`FK_636f17dadfea1ffb4a412296a28\``);
        await queryRunner.query(`DROP TABLE \`article\``);
    }

}

注意:上面的CreateArticle是固定的写法,表名是article,但是执行generate命令时需要在Article前加上Create表示创建表,可以是CreateUser,也可以说CreateRole,总之表名称一定要对应!

typeorm:run

表示执行迁移,上面通过create或者gen生成的文件都是迁移文件,通过执行命令:

npm run typeorm:run

然后就会执行对这些文件的逻辑(按道理,应该可以执行单个的,不过我暂时还未发现)。

暂时记录这些...