目 录CONTENT

文章目录

TypeORM synchronize 会丢数据的问题

Hello!你好!我是村望~!
2025-01-02 / 0 评论 / 0 点赞 / 14 阅读 / 2,039 字
温馨提示:
我不想探寻任何东西的意义,我只享受当下思考的快乐~

synchronize 会丢数据的问题

一开始玩 TypeOrm datasource 指定了 synchronize: true

只要创建或者修改了 Entity,那就会自动创建表和修改表结构。

image-20241230140547433

但是在生产环境下,用 synchronize 很危险,很容易丢数据。

比如创建了一个用户表 有 name age 字段 并且插入了数据

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

@Entity({
  name: 'nest_user',
})
export class User {
  @PrimaryGeneratedColumn()
  id: number;

  @Column({
    name: 'nest_name',
    length: 50,
  })
  name: string;

  @Column({
    name: 'nest_age',
    length: 50,
  })
  age: string;
}

id nest_name nest_age
1 cccc 1
2 cccc1 2
4 cccc3 3
5 cccc4 4
6 cccc5 5

然后你的 age 字段不想要了 注释掉

image-20241230141531506

Age那一列的数据就丢掉了

image-20241230141613350

更何况跑 nest 项目的时候都是用 npm run start:dev,代码改动立刻重新跑,所以很容易丢数据。

所以说,synchronize 在开发环境下确实很方便,但是在生产环境下不能用,不安全。

image-20241230141826825

那不用 synchonize 用啥呢,手动去数据库执行 sql 么?

那倒也不用。

可以用 TypeORM 的 migration 功能

migration 是迁移的意思

create table、alter table 这些都是 migration:只不过之前是自动跑,而现在我们要管理起来,手动跑。

migration:create

typeorm 提供了一个 cli,执行 migration:create 的命令:

npx ts-node ./node_modules/typeorm/cli migration:create ./src/migration/User

执行 生成了 “时间戳-User.ts” 文件,这个就是放迁移代码的:

➜  temp_nest-typeorm git:(main) ✗ npx ts-node ./node_modules/typeorm/cli migration:create ./src/migration/User

Migration /Users/xiaohao/FE/temp_nest-typeorm/src/migration/1735539989024-User.ts has been generated successfully.

src/migration/1735539989024-User.ts

import { MigrationInterface, QueryRunner } from 'typeorm';

export class User1735539989024 implements MigrationInterface {
  public async up(queryRunner: QueryRunner): Promise<void> {}

  public async down(queryRunner: QueryRunner): Promise<void> {}
}

迁移就是 create table、alter table 这些。

public async up(queryRunner: QueryRunner): Promise<void> 方法用于执行数据库更新的操作。当你运行数据库迁移命令(例如 typeorm migration:run),TypeORM 会按顺序调用各个迁移类的 up 方法,在这个方法中编写的代码会应用到数据库上,实现诸如创建表、添加列等操作。

import { MigrationInterface, QueryRunner, Table, TableColumn } from 'typeorm';

export class User1735539989024 implements MigrationInterface {
  public async up(queryRunner: QueryRunner): Promise<void> {
    const table = new Table({
      name: 'users',
      columns: [
        new TableColumn({
          name: 'id',
          type: 'int',
          isPrimary: true,
          isGenerated: true,
          generationStrategy: 'increment',
        }),
        new TableColumn({
          name: 'nest_name',
          type: 'varchar',
          length: '255',
          isNullable: false,
        }),
        new TableColumn({
          name: 'nest_age',
          type: 'int',
          isNullable: false,
        }),
      ],
    });
    // 使用 queryRunner 创建表
    await queryRunner.createTable(table);
  }

  public async down(queryRunner: QueryRunner): Promise<void> {}
}

Up 方法中 我们只是创建了 User 表!

这里先需要创建 src/data-source.ts 用于脚本指定的数据源 -d

import 'reflect-metadata';
import { DataSource } from 'typeorm';
import { User } from './user/entities/user.entity';

export const AppDataSource = new DataSource({
  type: 'mysql',
  host: 'localhost',
  port: 3306,
  username: 'root',
  password: '123',
  database: 'typeorm_test',
  synchronize: false,
  logging: true,
  entities: [User],
  migrations: ['./src/migration/**.ts'],
  subscribers: [],
  poolSize: 10,
  connectorPackage: 'mysql2',
  extra: {
    authPlugin: 'sha256_password',
  },
});

执行 migration:run

npx ts-node ./node_modules/typeorm/cli migration:run -d ./src/data-source.ts

image-20241230145354073

可以看到除了创建我们的 user 表 ,还创建了一个 migrations 用来纪律 migrations 的操作记录

migration:generate

但是还是很麻烦,首先需要migration:create:生成空白 migration 文件

还要我们定义实体,自己写表创建语句 new Table 的方式 or 写 SQL 在里面。

image-20241230151833862

万一定义的实体 entity 和 创表没对上呢!人总有犯错的时候!

所以最好是能根据实体生成 create 的脚本!

migration:generate 就是做这个的

migration:generate:连接数据库,根据 Entity 和数据库表的差异,生成 migration 文件

这里我把上面的长命令先封装到的 package.json

"typeormcli": "ts-node ./node_modules/typeorm/cli",
"migration:generate": "npm run typeormcli -- migration:generate -d ./src/data-source.ts",
"migration:run": "npm run typeormcli -- migration:run -d ./src/data-source.ts",
"migration:revert": "npm run typeormcli -- migration:revert -d ./src/data-source.ts"

执行 npm run migration:generate 会自动生成刚刚 create 创建的脚本,但是这次内部是根据 实体生成了创表 SQL 的!

➜  temp_nest-typeorm git:(main) ✗ npm run migration:generate ./src/migration/init

> temp_nest-typeorm@0.0.1 migration:generate
> npm run typeormcli -- migration:generate -d ./src/data-source.ts ./src/migration/init


> temp_nest-typeorm@0.0.1 typeormcli
> ts-node ./node_modules/typeorm/cli migration:generate -d ./src/data-source.ts ./src/migration/init

Ignoring invalid configuration option passed to Connection: authPlugin. This is currently a warning, but in future versions of MySQL2, an error will be thrown if you pass an invalid configuration option to a Connection
query: SELECT VERSION() AS `version`
query: SELECT DATABASE() AS `db_name`
query: SELECT `TABLE_SCHEMA`, `TABLE_NAME`, `TABLE_COMMENT` FROM `INFORMATION_SCHEMA`.`TABLES` WHERE `TABLE_SCHEMA` = 'typeorm_test' AND `TABLE_NAME` = 'nest_user'
query: SELECT * FROM `INFORMATION_SCHEMA`.`COLUMNS` WHERE `TABLE_SCHEMA` = 'typeorm_test' AND `TABLE_NAME` = 'typeorm_metadata'
Migration /Users/xiaohao/FE/temp_nest-typeorm/src/migration/1735546679217-init.ts has been generated successfully.

生成的内容

import { MigrationInterface, QueryRunner } from "typeorm";

export class Init1735546679217 implements MigrationInterface {
    name = 'Init1735546679217'

    public async up(queryRunner: QueryRunner): Promise<void> {
        await queryRunner.query(`CREATE TABLE \`nest_user\` (\`id\` int NOT NULL AUTO_INCREMENT, \`nest_name\` varchar(50) NOT NULL, \`nest_age\` varchar(50) NOT NULL, PRIMARY KEY (\`id\`)) ENGINE=InnoDB`);
    }

    public async down(queryRunner: QueryRunner): Promise<void> {
        await queryRunner.query(`DROP TABLE \`nest_user\``);
    }

}

然后我们执行 migration:run 就可以直接建表了!

【注意】这里同一个 migration 如果执行过的话 migrations 表会记录 就不会再次执行了,可以把 migrations 对应的记录删掉

image-20241230162013001

如果加了字段 or 改了字段

image-20241230162420024

重新执行 generate

npm run migration:generate ./src/migration/init

会生成新的 migration 内部是当前变更的 SQL 内容

import { MigrationInterface, QueryRunner } from "typeorm";

export class Init1735546998914 implements MigrationInterface {
    name = 'Init1735546998914'

    public async up(queryRunner: QueryRunner): Promise<void> {
        await queryRunner.query(`ALTER TABLE \`nest_user\` DROP COLUMN \`nest_age\``);
        await queryRunner.query(`ALTER TABLE \`nest_user\` ADD \`nest_age\` varchar(150) NOT NULL`);
    }

    public async down(queryRunner: QueryRunner): Promise<void> {
        await queryRunner.query(`ALTER TABLE \`nest_user\` DROP COLUMN \`nest_age\``);
        await queryRunner.query(`ALTER TABLE \`nest_user\` ADD \`nest_age\` varchar(50) NOT NULL`);
    }
}

执行migration:run

执行run之后,migrations 表会插入一条迁移记录

image-20241230163055580

会更新到数据库中!最终的表结构 DDL

create table typeorm_test.nest_user
(
    id               int auto_increment
        primary key,
    nest_name        varchar(50)  not null,
    nest_description varchar(150) not null,
    nest_age         varchar(150) not null
);

migration:revert

migration:revert 命令的主要作用是撤销上一次执行的数据库迁移

image-20241230163724517

刚好是和 up 方法反着的 毕竟是做恢复

image-20241230164438914

执行了恢复还会删掉 migrations 的run 的记录

总结

生产环境是通过 migration 来创建表、更新表结构、初始化数据的。

这节我们在 nest 项目里实现了下迁移。

大概有这几步:

  • 创建 data-source.ts 供 migration 用
  • 把 synchronize 关掉
  • 用 migration:generate 生成创建表的 migration
  • 用 migration:run 执行
  • 用 migration:create 创建 migration,然后填入数据库导出的 sql 里的 insert into 语句 (不常用,基本通过 entity 使用 generate 生成)
  • 用 migration:run 执行
  • 用 migration:generate 生成修改表的 migration
  • 用 migration:run 执行

这样,如何在生产环境通过 migration 创建表、修改表、初始化数据我们就都清楚了。

使用 dotenv 配置.env

现在两个地方都要用同一份配置的 data-source 可以通过配置文件来读!

安装依赖!

npm install --save-dev dotenv

创建 src/.env

# mysql 相关配置
mysql_server_host=localhost
mysql_server_port=3306
mysql_server_username=root
mysql_server_password=123
mysql_server_database=typeorm_test

data-source 读取配置信息

import 'reflect-metadata';
import { DataSource } from 'typeorm';
import { User } from './user/entities/user.entity';
import { config } from 'dotenv';
config({ path: './src/.env' });
export const AppDataSource = new DataSource({
  type: 'mysql',
  host: `${process.env.mysql_server_host}`,
  port: +`${process.env.mysql_server_port}`,
  username: `${process.env.mysql_server_username}`,
  password: `${process.env.mysql_server_password}`,
  database: `${process.env.mysql_server_database}`,
  synchronize: false,
  logging: true,
  entities: [User],
  migrations: ['./src/migration/**.ts'],
  subscribers: [],
  poolSize: 10,
  connectorPackage: 'mysql2',
  extra: {
    authPlugin: 'sha256_password',
  },
});

执行起来没什么问题!

image-20241230172411679

0

评论区