-
Notifications
You must be signed in to change notification settings - Fork 1
TypeORM: synchronize ๋์ migration
- ์์ฑ์: J045_๊น์ํธ
TypeORM์ DB์ ์ฐ๊ฒฐ์ํค๊ธฐ ์ํด์๋ DataSourceOption์ ์์ฑํด์ผํ๋ค. ์ด๋, DB์ Table๊ณผ Entity๋ค์ ์ฐ๋์ํค๊ธฐ ์ํด ์ฐ๋ฆฌ๋ ์ต์
์ synchronize: true์ ์ถ๊ฐํ๊ฑฐ๋, migration์ ์ํํ์ฌ์ผ ํ๋ค.
์ฌ๊ธฐ์ synchronize๋ ์ดํ๋ฆฌ์ผ์ด์
์ด ์คํ๋ ๋๋ง๋ค ๋ฐ์ดํฐ๋ฒ ์ด์ค ์คํค๋ง๋ฅผ ๋์ ์ผ๋ก ์์ฑํด์ฃผ๋ ๊ฒ์ ์๋ฏธํ๋ค. ๋ค์ ๋งํด Entity๋ฅผ ์ด์ฉํ์ฌ ํญ์ Table์ ์์ฑํด์ค๋ค๋ ๋ป์ด๋ค. ๋ค๋ง, ํด๋น ์ต์
์ Data ์์ค ๊ฐ๋ฅ์ฑ์ด ์๊ธฐ ๋๋ฌธ์ production ๋ ๋ฒจ์์๋ ์ฌ์ฉํด์๋ ์๋๋ฉฐ, ๋ฐ๋ผ์ ๊ฐ๋ฐ, ํน์ ๋๋ฒ๊น
๋ชฉ์ ์ผ๋ก ์ฌ์ฉํ ๊ฒ์ ๊ถ์ฅํ๋ค.
synchronize ์ต์
์ Entity์ ์ค์ ์ ๋์ผํ๊ฒ ํ๊ธฐ ์ํด ๋จ์ํ Column์ ADD/DROP ํ๋ ํ์์ผ๋ก ํ
์ด๋ธ์ ๋ณํํ๋ค. ๊ทธ๋ ๊ธฐ์ ๋ง์ฝ ํน์ Entity์ ์ด๋ฆ์ด A์์ B๋ก ๋ฐ๋์๋ค๋ฉด, sync ์ต์
์ด ์ผ์ง ๊ฒฝ์ฐ A Column์ Drop์ํค๊ณ , B Column์ Addํ๋ ์์ผ๋ก ์์
์ ํ๊ธฐ์ ๋ฐ์ดํฐ๊ฐ ์์ค๋๋ ์ํฉ์ด ๋ฐ์ํ๋ค. ์ด๋ฌํ ํ์์ Entity ๋ช
์ด ๋ฐ๋์ด๋ ๋์ผํ๊ฒ ์๊ธด๋ค. (์ด ์ํฉ์ ์ฌ์ง์ด Table ์์ฒด๋ฅผ DROP ์ํค๊ณ CREATE๋ฅผ ์ํํ๋ค. ๋ ์ํํด์ก๋ค.)
์ฐ๋ฆฌ๋ ํด๋น ์ต์
์ DataSource ๊ฐ์ฒด๋ฅผ ์ด๊ธฐํํ๋ ๊ณผ์ ์์ ์ต์
์ ์ผ๋ถ๋ก ์ฌ์ฉํ๋ค. ๊ทธ๋ ๋ค๋ฉด, synchronize ์ต์
์ด ํ์ฑํ๋์์ ๋ ์ด๋ค ๋์์ ํ๋์ง ์ดํด๋ณด๋ ๊ฒ์ด ์ข์ ๊ฒ์ด๋ค. ์ด๋ฅผ ์ํด TypeORM์ ๊นํ๋ธ ๋ ํฌ์งํ ๋ฆฌ๋ฅผ ์ดํด๋ณด๋ฉด, /src/data-source/DataSource.ts ์ DataSource ํด๋์ค๊ฐ ์ ์๋์ด์์ผ๋ฉฐ, ์ด ์ค synchronize ์ต์
๊ณผ ๊ด๋ จ๋ ๋ถ๋ถ์ initialize() ๋ฉ์๋์์ ํด๋น ์ต์
์ด ์ผ์ ธ์์ ๊ฒฝ์ฐ synchronize() ๋ฅผ ์คํ์ํค๋ ๋ถ๋ถ์ด๋ค. initialize์ synchronize ๋ฉ์๋์ ๋ช
์ธ๋ ๋ค์๊ณผ ๊ฐ๋ค.
/**
* Performs connection to the database.
* This method should be called once on application bootstrap.
* This method not necessarily creates database connection (depend on database type),
* but it also can setup a connection pool with database to use.
*/
async initialize(): Promise<this> {
if (this.isInitialized)
throw new CannotConnectAlreadyConnectedError(this.name)
// connect to the database via its driver
await this.driver.connect()
// connect to the cache-specific database if cache is enabled
if (this.queryResultCache) await this.queryResultCache.connect()
// set connected status for the current connection
ObjectUtils.assign(this, { isInitialized: true })
try {
// build all metadatas registered in the current connection
await this.buildMetadatas()
await this.driver.afterConnect()
// if option is set - drop schema once connection is done
if (this.options.dropSchema) await this.dropDatabase()
// if option is set - automatically synchronize a schema
if (this.options.migrationsRun)
await this.runMigrations({
transaction: this.options.migrationsTransactionMode,
})
// if option is set - automatically synchronize a schema
if (this.options.synchronize) await this.synchronize()
} catch (error) {
// if for some reason build metadata fail (for example validation error during entity metadata check)
// connection needs to be closed
await this.close()
throw error
}
return this
}async synchronize(dropBeforeSync: boolean = false): Promise<void> {
if (!this.isInitialized)
throw new CannotExecuteNotConnectedError(this.name)
if (dropBeforeSync) await this.dropDatabase()
const schemaBuilder = this.driver.createSchemaBuilder()
await schemaBuilder.build()
}๋ช
์ธ ์์์ initialize ๋ฉ์๋๋ synchronize์๊ฒ ์ด๋ ํ ๋งค๊ฐ๋ณ์๋ ์ ๋ฌํ์ง ์์ผ๋ฏ๋ก synchronize์ dropDatabase๋ ๋ฌด์ํด๋ ๋ ๊ฒ์ด๋ค. ๊ทธ๋ ๋ค๋ฉด ์ค์ง์ ์ผ๋ก Entity๋ฅผ DB์ ์ฐ๋ํ๋ ์ญํ ์ ์ํํ๋ ๊ฒ์ schemaBuilder์ผ ๊ฒ์ด๋ค.
์ฌ๊ธฐ์ driver๋ DriverFactory ๊ฐ์ฒด๋ก๋ถํฐ create ๋ฉ์๋๋ฅผ ์คํํจ์ผ๋ก์จ ์์ฑ๋๋ฉฐ(this.driver = new DriverFactory().create(this) - ์์ฑ์ ํจ์, ์ผ๋ถ ์๋ต), DriverFactory๋ DataSource์ type ์ต์
์ ๋ฐ๋ผ ์์ฑํ๊ณ , ๋๋ถ๋ถ์ RDBMS๋ค์ ๊ฒฝ์ฐ createSchemaBuilder๋ก๋ถํฐ RdbmsSchemaBuilder๊ฐ ์์ฑ๋๋ค.
์ถ์ ์ ๊ฑฐ๋ญํ ๊ฒฐ๊ณผ, ์ฐ๋ฆฌ๋ schemaBuilder.build()์ ๋ช
์ธ๋ฅผ ์ฐพ์๋ผ ์ ์์๊ณ , ์ด๋ ํ๋จ๊ณผ ๊ฐ๋ค.
/**
* Creates complete schemas for the given entity metadatas.
*/
async build(): Promise<void> {
this.queryRunner = this.connection.createQueryRunner()
// this.connection.driver.database || this.currentDatabase;
this.currentDatabase = this.connection.driver.database
this.currentSchema = this.connection.driver.schema
// CockroachDB implements asynchronous schema sync operations which can not been executed in transaction.
// E.g. if you try to DROP column and ADD it again in the same transaction, crdb throws error.
// In Spanner queries against the INFORMATION_SCHEMA can be used in a read-only transaction,
// but not in a read-write transaction.
const isUsingTransactions =
!(this.connection.driver.options.type === "cockroachdb") &&
!(this.connection.driver.options.type === "spanner") &&
this.connection.options.migrationsTransactionMode !== "none"
await this.queryRunner.beforeMigration()
if (isUsingTransactions) {
await this.queryRunner.startTransaction()
}
try {
await this.createMetadataTableIfNecessary(this.queryRunner)
// Flush the queryrunner table & view cache
const tablePaths = this.entityToSyncMetadatas.map((metadata) =>
this.getTablePath(metadata),
)
await this.queryRunner.getTables(tablePaths)
await this.queryRunner.getViews([])
await this.executeSchemaSyncOperationsInProperOrder()
// if cache is enabled then perform cache-synchronization as well
if (this.connection.queryResultCache)
await this.connection.queryResultCache.synchronize(
this.queryRunner,
)
if (isUsingTransactions) {
await this.queryRunner.commitTransaction()
}
} catch (error) {
try {
// we throw original error even if rollback thrown an error
if (isUsingTransactions) {
await this.queryRunner.rollbackTransaction()
}
} catch (rollbackError) {}
throw error
} finally {
await this.queryRunner.afterMigration()
await this.queryRunner.release()
}
}์์ ์ฝ๋๋ค ์ค์์ DB๋ฅผ ์กฐ์ํ๋ ๊ฒ์ this.executeSchemaSyncOperationsInProperOrder() ์ด๋ค. ์ด ๋ฉ์๋๋ฅผ ์ดํด๋ณด๋ฉด, ์ผ๋ถ ๊ฒฝ์ฐ๋ฅผ ์ ์ธํ๊ณ ๋ ์ปฌ๋ผ์ dropํ ๋ค add๋ฅผ ์ํํ๊ฑฐ๋, table์ dropํ ๋ค createํ๋ ํ์์ผ๋ก ๋์ํ๋ค.
/**
* Executes schema sync operations in a proper order.
* Order of operations matter here.
*/
protected async executeSchemaSyncOperationsInProperOrder(): Promise<void> {
await this.dropOldViews()
await this.dropOldForeignKeys()
await this.dropOldIndices()
await this.dropOldChecks()
await this.dropOldExclusions()
await this.dropCompositeUniqueConstraints()
// await this.renameTables();
await this.renameColumns()
await this.createNewTables()
await this.dropRemovedColumns()
await this.addNewColumns()
await this.updatePrimaryKeys()
await this.updateExistColumns()
await this.createNewIndices()
await this.createNewChecks()
await this.createNewExclusions()
await this.createCompositeUniqueConstraints()
await this.createForeignKeys()
await this.createViews()
}๊ทธ๋ ๋ค๋ฉด ์์ธ ์ผ์ด์ค๋ ๋ฌด์์ธ๊ฐ? renameColumns์ ๊ฒฝ์ฐ, ์ค๋ก์ง Table์์ ๋จ ํ๋์ Column๋ง์ด ๋ณ๊ฒฝ๋ ๊ฒฝ์ฐ์๋ Rename์ผ๋ก ์ฒ๋ฆฌํ๋ฉฐ, ์ด๋ฌํ ์ฒ๋ฆฌ๋ฅผ ๊ฐ Table์ ๋ํด ์ํํ๋ ์์ผ๋ก ๋์ํ๋ค. ๊ฐ๋จํ๊ฒ ํ์
ํ๊ณ ์ ํ ๊ฒฝ์ฐ ์ฃผ์์ ์ฐธ๊ณ ํ๋ค.
renameColumns์ ์ฃผ์ (์ธ๋ถ ์ฒ๋ฆฌ๋ ์ฝ๋๋ฅผ ํ์ธํ ๊ฒ.)
- Renames columns.
- Works if only one column per table was changed.
- Changes only column name. If something besides name was changed, these changes will be ignored.
๋ฐ๋ผ์ Entity ๋ด์์ ์ฌ๋ฌ ์ปฌ๋ผ์ด ๋ณ๊ฒฝ๋ ๊ฒฝ์ฐ Column์ Dropํ ๋ค Addํ๋ ํ์์ผ๋ก ์ฒ๋ฆฌ๋ ๊ฒ์ด๋ผ ์ง์ํ ์ ์๋ค.
๋ฉ์๋ ๋ช
์ด updateExistColumns์ฌ์ ์ปฌ๋ผ์ ๋ฉํ๋ฐ์ดํฐ๋ง ๋ณ๊ฒฝ๋ ๊ฒ ๊ฐ์ ์ด ๋ฉ์๋๋ changeColumns๋ผ๋ QueryRunner์ ๋ฉ์๋๋ฅผ ์ฌ์ฉํ๋๋ฐ, ํด๋น ํจ์๋ฅผ ์ดํด๋ณด๋ฉด ์ผ๋ถ ์กฐ๊ฑด์ ๊ฒฝ์ฐ Drop ํ Add๋ฅผ ์ํํ๋ ๊ฒ์ ๋ณผ ์ ์๋ค. ์ด ์ธ์ ๊ฒฝ์ฐ ALTER ~ CHANGE COLUMN ํ์์ผ๋ก ์ฒ๋ฆฌ๋๋ค.
-
Column์ ํ์ ์ด ๋ณ๊ฒฝ๋์์.
-
Column์ length(ํน์ size)๊ฐ ๋ณ๊ฒฝ๋จ. (ex. varchar(50)โvarchar(60))
-
๊ธฐ์กด๊ณผ ์ ์ปฌ๋ผ ๋ ๋ค
generatedType์ด ์ ์๋์ด์์ง๋ง, ๋์generatedType์ด ์๋ก ๋ค๋ฆ. -
generatedType์ดVIRTUAL์์undefined๊ฐ ๋๊ฑฐ๋, ๊ทธ ์ญ์ผ ๊ฒฝ์ฐ์ด๋ ๊ฒ
synchronize๋ฅผ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ ๋ฐ์ดํฐ๋ฅผ ์ต๋ํ ๋ณด์กดํด์ฃผ๊ธฐ ์ํด ์์ ์ฅ์น๋ฅผ ์ต๋ํ ๋ง๋ จํ์ง๋ง, ๊ทธ๋ผ์๋ ๋ถ๊ตฌํ๊ณ Column ์์ฒด๊ฐ ๋ ์๊ฐ๋ค๊ฐ ์ถ๊ฐ๋๋ ํ์์ด ๋ฐ์ํ ์๋ ์๊ธฐ ๋๋ฌธ์,synchronize๋ ์์ ํ์ง ์๋ค๊ณ ํ๋ ๊ฒ์ด๋ค.์ด์ ๋ฐํ์ฌ migration์ ๊ฒฝ์ฐ ๊ฐ๋ฐ์๊ฐ ์ง์ ์ด์ Entity์์ ๋ณ๊ฒฝ๋ Entity๋ก ์์ ํ๊ธฐ ์ํด ํ์ํ ์ฟผ๋ฆฌ๋ฌธ์ ์ง์ ์์ฑํ๊ธฐ ๋๋ฌธ์, ๋ฐ์ดํฐ ์์ค์์ ๊ทธ๋๋ง ์์ ํ๋ค๊ณ ํ ์ ์๋ ๊ฒ์ด๋ค.
๋ง์ด๊ทธ๋ ์ด์
์ ํ๊ธฐ ์ํด์๋ ์ด๋ค ํด๋์ ๋ง์ด๊ทธ๋ ์ด์
์ ๋ณด๊ฐ ๋ด๊ฒจ์๊ณ , ์ด๋ค Table์ ์ฌํ๊น์ง ์คํํด์๋ ๋ง์ด๊ทธ๋ ์ด์
๋ค์ ๋ํ ์ ๋ณด๊ฐ ๋ด๊ฒจ์๋์ง ์ ์ํด์ผ ํ๋ค. ์ด๋ฌํ ์ ๋ณด๋ค์ DataSourceOption์์ ์์ฑํ ์ ์์ผ๋ฉฐ, ํด๋น ์ต์
์ Nest.js์ ๊ฒฝ์ฐ TypeOrmModule.forRoot()์์ ์์ฑํ๊ฑฐ๋, ์๋๋ฉด config ํ์ผ์ ๋ฐ๋ก ์์ฑํ์ฌ ์ด๋ฅผ ์ด์ฉํ ์ ์๋ค. (๋ณดํต์ ormconfig.ts๋ก ์ด๋ค.)
DataSourceOption์๋ DBMS ํ์
, DB ์ ๊ทผ ๋ฐฉ๋ฒ, ์ฌ์ฉํ Entity, migrations, migrationsTableName์ ์ ์ํ์ฌ DB์ Entity์์ ์ฐ๋ ๋ฐ ๋ง์ด๊ทธ๋ ์ด์
์ฒ๋ฆฌ๋ฅผ ์ ์ํ ์ ์๋ค.
-
migrations- ๋ง์ด๊ทธ๋ ์ด์
์ ์ํํ
MigrationInterface๊ตฌํ์ฒด ํ์ผ ๋ชฉ๋ก. - ์ผ๋ฐ์ ์ผ๋ก ๋ง์ด๊ทธ๋ ์ด์ ํ์ผ๋ค์ ์ ์ฅํ ํ์ผ ๊ฒฝ๋ก๋ฅผ ์ ์ฅํ๋ค.
- ex.
migrations: ['src/migrations/**/*.ts']
- ๋ง์ด๊ทธ๋ ์ด์
์ ์ํํ
-
migrationsTableName- ์ด๋ฏธ ๋ง์ด๊ทธ๋ ์ด์
์ ์ฌ์ฉํ
MigrationInterface์ ๋ชฉ๋ก์ ์ ์ฅํ ํ ์ด๋ธ ์ด๋ฆ. - ๋ฏธ์ง์ ์
migrations๋ก ์ง์ ๋๋ค. - ex.
migrationsTableName: "migrations"
๋ง์ด๊ทธ๋ ์ด์ ์ฒ๋ฆฌ ์ TypeORM์ DB์ ๋ฐ์ํ์ง ์์ (=Pending ์ํ์ธ)
MigrationInterface๊ตฌํ์ฒด๋ค์ ์ถ์ถํ์ฌ ์ด๋ค์ ๊ฐ์ฅ ์ค๋๋ ๊ฒ๋ค๋ถํฐ ์คํํ๋ค. ๋ง์ด๊ทธ๋ ์ด์ ๊ณผ์ ์ ๋ชจ๋ Transaction์ผ๋ก ์ฒ๋ฆฌ๋๋ฉฐ, ๋ฐ๋ผ์ ๋์ค์ ์คํจํ๋ค๊ณ ํ๋ค Table์ด ๊นจ์ ธ๋ฒ๋ฆฌ๋ ๋ถ์์ฌ๋ ๋ฐ์ํ์ง ์๋๋ค.์ด๋ฅผ ์ ์ฉํ ํ๋ก์ ํธ์
ormconfig.ts๋ ๋ค์๊ณผ ๊ฐ์ด ์์ฑํ์๋ค. ์ฃผ์ํ ์ ์, ํด๋น ํ์ผ์์ exportํ๋DataSource๋ ๋ฐ๋์ ๋จ ํ๊ฐ๋ง ์กด์ฌํ์ฌ์ผ ํ๋ฉฐ, ๋ฐ๋์ ์กด์ฌํ์ฌ์ผ ํ๋ค. - ์ด๋ฏธ ๋ง์ด๊ทธ๋ ์ด์
์ ์ฌ์ฉํ
import { join } from 'path';
import { DataSource, DataSourceOptions } from 'typeorm';
import * as dotenv from 'dotenv';
dotenv.config();
export const config: DataSourceOptions = {
type: 'mysql',
host: process.env.MYSQL_HOST,
port: parseInt(process.env.MYSQL_PORT),
username: process.env.MYSQL_USERNAME,
password: process.env.MYSQL_PASSWORD,
database: process.env.MYSQL_DATABASE,
entities: [join(__dirname, '/**/*.entity{.ts,.js}')],
migrationsRun: true,
migrations: [process.env.NODE_ENV === 'develop' ? 'src/migrations/**/*.ts' : 'dist/migrations/**/*.js'],
migrationsTableName: 'migrations',
synchronize: process.env.NODE_ENV === 'develop',
};
export default new DataSource(config);๋ง์ด๊ทธ๋ ์ด์
์ ์๋์ผ๋ก ์ํํ๋ ค๋ฉด, typeorm-cli๊ฐ ํ์ํ๋ค. ์ด๋ฅผ ๋ฐ๋ก ์ค์นํ ํ์๋ ์๊ณ , TypeScript๋ฅผ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ package.json์ script๋ก ๋ฑ๋กํ์ฌ ์ฌ์ฉํ๊ฒ ๋๋ค. (JS๋ฅผ ์ฐ๋ ๊ฒฝ์ฐ ๋จ์ํ typeorm ์ค์น ํ npx typeorm <param> ํ์์ผ๋ก ์ฌ์ฉํ๋ค.)
TypeORM์ CLI๋ ์ค๋ก์ง js ํ์ผ์ ์คํํ ๋ชฉ์ ์ผ๋ก ์์ฑ๋์๊ธฐ์, TypeScript ํ๋ก์ ํธ์์๋ ์ด๋ฅผ ํธ๋์คํ์ผ๋งํ ๋๊ตฌ๊ฐ ํ์ํ๋ค. ๊ทธ๋ ๊ธฐ์ ts-node๋ฅผ ์ค์นํ ๋ค, package.json์ ๋ค์๊ณผ ๊ฐ์ด ์คํฌ๋ฆฝํธ๋ฅผ ์ถ๊ฐํ๋ฉฐ, ์ถํ npx typeorm ์ด๋ npm run typeorm ์ด๋ผ๋ ์์ผ๋ก CLI๋ฅผ ์ฌ์ฉํ๋ค.
// commonjs ํ๋ก์ ํธ์ผ ๊ฒฝ์ฐ
"script": {
// ...
"typeorm": "typeorm-ts-node-commonjs"
}
// ESM ํ๋ก์ ํธ์ผ ๊ฒฝ์ฐ
"script": {
// ...
"typeorm": "typeorm-ts-node-esm"
}๋ง์ฝ npm ์คํฌ๋ฆฝํธ์๊ฒ ๋๊ฒจ์ผ ํ ํ๋ผ๋ฏธํฐ๊ฐ ์์ ๊ฒฝ์ฐ, --์ ์ถ๊ฐํ๋ค. ์ผ๋ฐ์ ์ผ๋ก ๋ค์๊ณผ ๊ฐ์ด ๋ช
๋ น์ ์ํํ๋ค.
# Case 1
npm run typeorm -- migration:run -d <dataSource File location>
# Case 2
npm run typeorm migration:run -- -d <dataSource File location>์คํ ์์ ๋ ๋ค์๊ณผ ๊ฐ๋ค.
# ๋ง์ด๊ทธ๋ ์ด์
๊ด๋ จ ๋ช
๋ น =========================================
# ๋ง์ด๊ทธ๋ ์ด์
์คํ
npm run typeorm -- migration:run -d <dataSource File location>
# ๊ฐ์ฅ ์ต๊ทผ์ ์คํ๋ migrationInterface ๊ตฌํ์ฒด์ ์คํ์ ์ทจ์
npm run typeorm -- migration:revert -d <dataSource File location>
# ๋ง์ด๊ทธ๋ ์ด์
๋ชฉ๋ก ์ถ๋ ฅ (๋ฐ์ = [X], ๋ฏธ๋ฐ์ = [ ])
npm run typeorm -- migration:show -d <dataSource File location>
# ๋ง์ด๊ทธ๋ ์ด์
ํ
ํ๋ฆฟ ์์ฑ
npm run typeorm -- migration:create <์ ์ฅ ๋๋ ํ ๋ฆฌ ๊ฒฝ๋ก>/<template ํ์ผ๋ช
>
# ๋ง์ด๊ทธ๋ ์ด์
์๋ ์์ฑ
npm run typeorm -- migration:generate <์ ์ฅ ๋๋ ํ ๋ฆฌ ๊ฒฝ๋ก>/<template ํ์ผ๋ช
>
# ์ฐธ๊ณ ==========================================================
# synchronization ์ํ (synchronize=true ์์ ์ฒ๋ฆฌ๋ฅผ ์๋์ผ๋ก ์คํ, ์ฃผ์ ์๊ตฌ)
npm run typeorm -- schema:sync
# sync ์๊ตฌ ์ ์ฒ๋ฆฌํ ์ฟผ๋ฆฌ๋ฌธ ๋ชฉ๋ก ํ์ธ (๊ฐ์ง๋ก sync๋ฅผ ์คํํ๋ค.)
npm run typeorm -- schema:log์์ CLI ์์ ๋ฅผ ๋ณด๋ฉด, migration:create๋ ๋ง์ด๊ทธ๋ ์ด์
ํ
ํ๋ฆฟ์ ์์ฑํ๋ฉฐ, migration:generate๋ ํ์ฌ Entity์ DB์ ์คํค๋ง๋ฅผ ์๋ก ๋น๊ตํ์ฌ, ์คํํ์ฌ์ผํ ์ฟผ๋ฆฌ๋ฌธ๋ค์ ์์ฑํ์ฌ ์ด๋ฅผ ๋ง์ด๊ทธ๋ ์ด์
์ผ๋ก ์ถ๋ ฅํ๋ค. ๋ ๋ช
๋ น ๋ชจ๋ ์ด๋ฆ์ ์ผ๋ฐฅ ์ผ์ด์ค๋ก ์ด๋ฆ์ ์ง์ ํด์ฃผ๋ฉด, ๋ง์ด๊ทธ๋ ์ด์
ํ์ผ ์ด๋ฆ์ {TIME_STAMP}-{Kebap-case-name}.ts ํ์์ผ๋ก ๋ง์ด๊ทธ๋ ์ด์
ํ์ผ์ ์์ฑํ๋ฉฐ, ๊ตฌํ์ฒด์ ์ด๋ฆ์ {camelCaseName}{TIME_STAMP} ํ์์ผ๋ก ์์ฑํด์ค๋ค.
ํ์ง๋ง ๋ด์ฉ๋ฌผ์ ์กฐ๊ธ ๋ค๋ฅด๋ค. migration:create์ ๊ฒฝ์ฐ ๋ง์ด๊ทธ๋ ์ด์
์ ๋ณด์ผ๋ฌ ํ๋ ์ดํธ๋ง์ ์์ฑํ๋ฉฐ, ๋ด์ฉ๋ฌผ์ ๊ฐ๋ฐ์๊ฐ ์ง์ ์ฑ์ฐ๋๋ก ์๊ตฌํ๋ค. ์๋ ์ฝ๋๋ ๋ง์ด๊ทธ๋ ์ด์
์ ๋ณด์ผ๋ฌ ํ๋ ์ดํธ, ๋ค์ ๋งํด migration:create๋ก ์์ฑ๋ ํ์ผ์ ๋ด์ฉ๋ฌผ์ด๋ค.
import { MigrationInterface, QueryRunner } from 'typeorm';
export class addThumbnailOnWorkspace1669037051304 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {}
public async down(queryRunner: QueryRunner): Promise<void> {}
}MigrationInterface๋ ๊ตฌํ์ฒด์๊ฒ up๊ณผ down์ ๊ตฌํํ ๊ฒ์ ์๊ตฌํ๋ค. ํด๋น ๋ฉ์๋๋ค์๊ฒ๋ DataSource๋ฅผ ์ด์ฉํ์ฌ DB์ ์ฐ๊ฒฐ๋ QueryRunner๊ฐ ์ฃผ์
๋๋ฉฐ, ์ด๋ฅผ ์ด์ฉํ์ฌ ์ฟผ๋ฆฌ๋ฅผ ์ ์ํ๋ฉด ๋๋ค.
-
up- ๋ง์ด๊ทธ๋ ์ด์ ์คํ ์ ์ฒ๋ฆฌํ ์ฟผ๋ฆฌ๋ฅผ ์ ์ํ๋ค.
-
migration:run์คํ ์ ์ฒ๋ฆฌํ ์ฟผ๋ฆฌ๋ฅผ ์์ฑํ๋ค.
-
down- ๋ง์ด๊ทธ๋ ์ด์ ์ ์ทจ์ํ๊ณ ์ ํ ๋ ์คํํ ์ฟผ๋ฆฌ๋ฅผ ์ ์ํ๋ค.
-
migration:revert์คํ ์ ์ฒ๋ฆฌํ ์ฟผ๋ฆฌ๋ฅผ ์ ์ํ๋ค.
ํ๋จ์ ์ฝ๋๋ Entity์ Column์ด ํ๋ ์ถ๊ฐ๋จ์ ๋ฐ๋ผ ํ๋ก์ ํธ์์ ๋ณด์ผ๋ฌ ํ๋ ์ดํธ์ ์์ฑํ ์ฝ๋์ด๋ค.
import { MigrationInterface, QueryRunner, TableColumn } from 'typeorm';
export class addThumbnailOnWorkspace1669037051304 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.addColumn(
'workspace',
new TableColumn({
name: 'thumbnail',
type: 'varchar',
length: '2083',
isNullable: true,
}),
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.dropColumn('workspace', 'thumbnail');
}
}์ด์ ๋ฐํด migration:generate๋ DB์ ์คํค๋ง์ ํ Entity๋ฅผ ๋น๊ตํ์ฌ ์ฟผ๋ฆฌ๊ฐ ์ด๋ฏธ ์์ฑ๋์ด์๋ ๋ง์ด๊ทธ๋ ์ด์
ํ์ผ์ ์๋์ผ๋ก ์์ฑํด์ค๋ค. ๋ฐ๋ผ์ ๊ฐ๋ฐ์๊ฐ ์ง์ ๋ง์ด๊ทธ๋ ์ด์
์ฝ๋๋ฅผ ์์ฑํ ํ์๊ฐ ์๋ค. ๋ค๋ง ์ฃผ์ํ ์ ์, ์ด ๋ํ synchronize์ ๋น์ทํ ์ฝ๋๊ฐ ๋์ถ๋ ์ ์๊ธฐ์ ๋ฐ์ดํฐ ์์ค์ ์ฐ๋ ค๊ฐ ๋ฐ์ํ ์ ์์ผ๋ฉฐ, ๋ฐ๋ผ์ generate๋ ํ์ผ์ ์ฟผ๋ฆฌ๋ฌธ์ ์ง์ ํ์ธํด๋ณผ ํ์๊ฐ ์๋ค.
๋ง์ด๊ทธ๋ ์ด์
์ QueryRunner๋ฅผ ํ์ฉํ๋ฏ๋ก, ์์ฑ ์์ ์ด์ API๋ฅผ ์ด๋์ ๋ ์ธ์งํ ํ์๊ฐ ์๋ค. ์์ธํ ์ฌํญ์ ๋งํฌ๋ฅผ ์ฐธ์กฐํ๋ค.
- ๋ง์ด๊ทธ๋ ์ด์
์ ์๋ํ๋ฉด MODULE_NOT_FOUND ์ค๋ฅ๊ฐ ๋ฐ์
- ํ๋ก์ ํธ ๋ด๋ถ์์ ๋ชจ๋๋ค์ ๋ชจ๋ ์๋๊ฒฝ๋ก๋ก import ํ์๋์ง ํ์ธ.
- ์ ๋๊ฒฝ๋ก๋ก import ๋ ๋ชจ๋์ด ์๋ ๊ฒฝ์ฐ ์์ ์ค๋ฅ๊ฐ ๋ฐ์ํจ.
- https://stackoverflow.com/questions/66991600/typeorm-migration-error-cannot-find-module
- https://typeorm.io/data-source-options
- https://www.reddit.com/r/typescript/comments/pp86s8/can_some_one_explain_to_me_migration_vs/
- https://github.com/typeorm/typeorm
- https://typeorm.io/using-cli
- https://github.com/typeorm/typeorm/blob/4ec04fa1205ec9587946869c56077dae5454a063/src/migration/MigrationExecutor.ts#L14
๋ฐ์ผ๋ฆฌ ์คํฌ๋ผ
- [Week1-Day1] ํ ๋น๋ฉ
- [Week1-Day2] ๋ฐ์ผ๋ฆฌ ์คํฌ๋ผ
- [Week1-Day3] ๋ฐ์ผ๋ฆฌ ์คํฌ๋ผ
- [Week1-Day4] ๋ฐ์ผ๋ฆฌ ์คํฌ๋ผ
- [Week1-Day5] ๋ฐ์ผ๋ฆฌ ์คํฌ๋ผ
- [Week2-Day1] ์คํ๋ฆฐํธ ๊ณํ ํ์
- [Week2-Day2] ๋ฐ์ผ๋ฆฌ ์คํฌ๋ผ
- [Week2-Day3] ๋ฐ์ผ๋ฆฌ ์คํฌ๋ผ
- [Week2-Day4] ๋ฐ์ผ๋ฆฌ ์คํฌ๋ผ
- [Week3-Day1] ์คํ๋ฆฐํธ ๊ณํ ํ์
- [Week3-Day2] ๋ฐ์ผ๋ฆฌ ์คํฌ๋ผ
- [Week3-Day3] ๋ฐ์ผ๋ฆฌ ์คํฌ๋ผ
- [Week3-Day4] ๋ฐ์ผ๋ฆฌ ์คํฌ๋ผ
- [Week4-Day1] ์คํ๋ฆฐํธ ๊ณํ ํ์
- [Week4-Day2] ๋ฐ์ผ๋ฆฌ ์คํฌ๋ผ
- [Week4-Day3] ๋ฐ์ผ๋ฆฌ ์คํฌ๋ผ
- [Week4-Day4] ๋ฐ์ผ๋ฆฌ ์คํฌ๋ผ
- [Week5-Day1] ์คํ๋ฆฐํธ ๊ณํ ํ์
- [Week5-Day2] ๋ฐ์ผ๋ฆฌ ์คํฌ๋ผ
- [Week5-Day3] ๋ฐ์ผ๋ฆฌ ์คํฌ๋ผ
- [Week5-Day4] ๋ฐ์ผ๋ฆฌ ์คํฌ๋ผ
- [Week6-Day1] ์คํ๋ฆฐํธ ๊ณํ ํ์
- [Week6-Day2] ๋ฐ์ผ๋ฆฌ ์คํฌ๋ผ
- [Week6 Day3] ๋ฐ์ผ๋ฆฌ ์คํฌ๋ผ
- [Week6 Day4] ๋ฐ์ผ๋ฆฌ ์คํฌ๋ผ