synchronize 会丢数据的问题
一开始玩 TypeOrm datasource 指定了 synchronize: true
只要创建或者修改了 Entity,那就会自动创建表和修改表结构。
但是在生产环境下,用 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 字段不想要了 注释掉
Age那一列的数据就丢掉了
更何况跑 nest 项目的时候都是用 npm run start:dev,代码改动立刻重新跑,所以很容易丢数据。
所以说,synchronize 在开发环境下确实很方便,但是在生产环境下不能用,不安全。
那不用 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
可以看到除了创建我们的 user 表 ,还创建了一个 migrations 用来纪律 migrations 的操作记录
migration:generate
但是还是很麻烦,首先需要migration:create:生成空白 migration 文件
还要我们定义实体,自己写表创建语句 new Table 的方式 or 写 SQL 在里面。
万一定义的实体 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 对应的记录删掉
如果加了字段 or 改了字段
重新执行 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 表会插入一条迁移记录
会更新到数据库中!最终的表结构 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
命令的主要作用是撤销上一次执行的数据库迁移
刚好是和 up 方法反着的 毕竟是做恢复
执行了恢复还会删掉 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',
},
});
执行起来没什么问题!
评论区