본문 바로가기

[NestJS]

[NestJS] - 6편 (DB연결하기 TypeORM 트랜잭션 마이그레이션)

반응형

NestJS - 6편 (DB연결하기 TypeORM 트랜잭션 마이그레이션)

관련 라이브러리 설치하기

npm install --save @nestjs/typeorm typeorm@0.2
Typeorm 버전을 0.2로 명시한 이유는

https://docs.nestjs.com/techniques/database#typeorm-integration

WARNING
Note that we're using TypeORM v0.2, which isn't the latest version of TypeORM. The latter has substantial modifications and duplicate methods which are used on this page. You can read about typeorm@0.3.0 changes on their repository.

에 따르면 아직 0.2버전을 지원하는 것으로 나와있다.

자신이 좋아하는 db는 다들 다르니... 난 흔한 mysql을 이용하겠다.
npm i mysql2

DB 연결하기

DB 연결하는 방법에는 크게 3가지가 있다.

  • 접속정보를 직접 파일에 명시해 연결
  • dotenv로 접속정보를 관리
  • ormconfig로 접속정보를 관리

접속정보를 직접 파일에 명시해 연결

app.module.ts파일에서 접속정보를 설정해준다.

import { TypeOrmModule } from '@nestjs/typeorm';

@Module({
  imports: [
        ...
    TypeOrmModule.forRoot({
      type: 'mysql',
      host: 'localhost',
      port: 3306,
      username: 'root',
      password: 'test',
      database: 'test',
      entities: [__dirname + '/**/*.entity{.ts,.js}'],
      synchronize: true,
    }),
  ],
})
export class AppModule {}

끝!

dotenv로 접속정보를 관리

app.module.ts파일에서 dotenv를 연결해준다.

import { TypeOrmModule } from '@nestjs/typeorm';

@Module({
  imports: [
        ...
    TypeOrmModule.forRoot({
      type: 'mysql',
            host: process.env.DATABASE_HOST, //'localhost',
      port: 3306,
      username: process.env.DATABASE_USERNAME, //'root',
      password: process.env.DATABASE_PASSWORD, //'test',
      database: 'test',
      entities: [__dirname + '/**/*.entity{.ts,.js}'],
            synchronize: Boolean(process.env.DATABASE_SYNCHRONIZE), // true,
    }),
  ],
})
export class AppModule {}

ormconfig로 접속정보를 관리

주의! 엔티티의 경로를 __dirname으로 못읽기 때문에 dist를 이용해야한다.
ormconfig.json 파일

{
  "type": "mysql",
  "host": "localhost",
  "port": 3306,
  "username": "root",
  "password": "test",
  "database": "test",
  "entities": ["dist/**/*.entity{.ts,.js}"],
  "synchronize": true
}

app.module.ts파일

import { TypeOrmModule } from '@nestjs/typeorm';

@Module({
  imports: [
    TypeOrmModule.forRoot(),
  ],
})
export class AppModule {}

CRUD는 다른 이쁘게 정리된 글들이 많아서 패스..!

네.. 패스합니다.. ㅠㅠ

트랜잭션

트랜잭션이란?

만일 입금을 한다고 해보자 A은행에서 B은행으로...

  1. A은행 계좌에서 출금
  2. B은행 계좌에 입금
    이런 절차를 따라 진행될 것이다. 그런데 A은행에서 출금이 됐는데 B은행에서 오류가 생겨 입금이 안됬다면????
    우리는 다시 A 출금전으로 상태를 돌려놔야할 필요가 있다.
    이렇게 플로우에 있어 이전 상태로 되돌리는 기능을 트랜잭션이라고 한다.

트랜잭션 방법

TypeORM에서 트랜잭션을 사용하는 방법은 3가지가 있다.

  • QueryRunner를 이용하는 방법
  • transaction 객체를 생성해서 이용하는 방법
  • @Transaction, @TransactionManager, @TransactionRepository 데코레이터를 사용하는 방법

셋 중에 나는 스프링 어노테이션처럼 데코레이터 방법을 이용하려했으나
데코레이터를 이용하는 방법은 Nest에서 권장하지 않는다고한다. 그러니 패스..!

QueryRunner 클래스를 사용하는 방법

user.service.ts 상단에 Connection 객체 주입!

...
import { Connection, ... } from 'typeorm';

@Injectable()
export class UsersService {
  constructor(
        ...
        private connection: Connection,
  ) { }
    ...
}

user.service.ts의 트랜잭션 작업할 서비스 항목에서
코드를 읽어보면 직관적이다. DB 쿼리 작업을 많이 해봤다면

  1. 내가 실수할수도 있으니 트랜잭션 걸고
  2. 쿼리 작성해서 실행하고
  3. 잘됐으면 커밋 or 틀렸으면 롤백
    이런 과정으로
private async saveUserUsingQueryRunner(name: string, email: string, password: string, signupVerifyToken: string) {
  const queryRunner = this.connection.createQueryRunner();

  await queryRunner.connect();
  await queryRunner.startTransaction();

  try {
    const user = new UserEntity();
    user.id = ulid();
    user.name = name;
    user.email = email;
    user.password = password;
    user.signupVerifyToken = signupVerifyToken;

    await queryRunner.manager.save(user);

    // throw new InternalServerErrorException(); // 일부러 에러를 발생시켜 본다

    await queryRunner.commitTransaction();
  } catch (e) {
    // 에러가 발생하면 롤백
    await queryRunner.rollbackTransaction();
  } finally {
    // 직접 생성한 QueryRunner는 해제시켜 주어야 함
    await queryRunner.release();
  }
}

transaction 객체를 생성해서 이용하는 방법

private async saveUserUsingTransaction(name: string, email: string, password: string, signupVerifyToken: string) {
  await this.connection.transaction(async manager => {
    const user = new UserEntity();
    user.id = ulid();
    user.name = name;
    user.email = email;
    user.password = password;
    user.signupVerifyToken = signupVerifyToken;

    await manager.save(user);

    // throw new InternalServerErrorException();
  })
}

마이그레이션

서비스의 코드가 업데이트 될 때마다 버전 관리를 하듯,
마찬가지로 DB도 업데이트 될 때마다 버전 관리가 필요하다.

TypeOR은 마이그레이션을 쉽게 할 수 있는 기능을 제공한다.

  1. 마이그레이션을 위한 쿼리문을 직접 작성하지 않아도 된다.
  2. 마이그레이션이 잘못되었다면 간단한 명령어도 되돌릴 수 있다.

기존에 유명한 다른 마이그레이션 툴로는 Sequelize도 있다.

코드설정

npm i typeorm 이전에 설치한 typeorm내의 cli를 이용할것이다.
npm i -g ts-node

package.json 파일 설정

"scripts": {
    ...
    "typeorm": "node --require ts-node/register ./node_modules/typeorm/cli.js"
}

ormconfig.json 마이그레이션 옵션 추가
synchronize : 시작될 때마다 DB Schema 자동생성 여부 설정(ex.true면 유저 테이블이 계속 생긴다.)

{
    ...
  "synchronize": false,
  "migrations": ["dist/migrations/*{.ts,.js}"],
  "cli": {
    "migrationsDir": "src/migrations"
  },
  "migrationsTableName": "migrations"
}

마이그레이션 생성

마이그레이션 파일 생성은 2가지 방법이 있다.

  • migration:create: 수행할 마이그레이션 내용이 비어있는 파일을 생성합니다.
  • migration:generate: 현재 소스코드와 migrations 테이블에 기록된 이력을 기반으로 마이그레이션 파일을 자동 생성합니다.

npm run typeorm migration:create -- -n CreateUserTable or
npm run typeorm migration:generate -- -n CreateUserTable

새로 쿼리 작성하기 귀찮으니 generate 명령어로 실행하면 알아서 up: 마이그레이션 실행코드, down:마이그레이션 되돌리는 코드가 생성되는 것을 알 수 있다.

  • npm run typeorm migration:run: 마이그레이션이 수행되고 마이그레이션 테이블이 생성되며 마이그레이션 이력이 기록됨.
  • npm run typeorm migration:revert: 마지막 마이그레이션 수행된 이력이 되돌려지며 마이그레이션 테이블의 마지막 이력이 삭제됨

참조 : NestJS로 배우는 백엔드 프로그래밍

반응형

'[NestJS]' 카테고리의 다른 글

[NestJS] - 8편 (JWT)  (0) 2022.04.21
[NestJS] - 7편 (미들웨어)  (0) 2022.04.21
[NestJS] - 5편 (파이프)  (0) 2022.04.20
[NestJS] - 4편 (모듈~config파일)  (0) 2022.04.20
[NestJS] - 3편 (프로바이더)  (0) 2022.04.19