-
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] ๋ฐ์ผ๋ฆฌ ์คํฌ๋ผ