본문 바로가기
포트폴리오

좋은 피자 위대한 피자 (2) (Type ORM 적용하여 entity 설정하기)

by Yura 🌼 2025. 1. 16.
728x90

안녕하세요 반갑습니다.

오늘은

  1. erd 변경사항과 수정 이유
  2. typeORM 소개 및 적용
  3. DB 관계 정의하기에 대해 알아보겠습니다.

1. 같은 테이블을 바라보기 때문에 중복된 컬럼을 삭제했습니다.

수정 전 테이블) food_id와 ingredient_id 두 컬럼이 order테이블을 향하고 있다.

기존의 연결테이블입니다. 여기서 script_id는 food_id와 동일한 주문 테이블을 바라보기 때문에 food_id와 ingredient_id 를 order_id로 수정하여 컬럼이 중복되는 것을 방지하고자 수정했습니다.

 

수정 후 테이블) order_id로 변경하여 number형으로 피자의 이름을 나타내게 설정하였다.

수정한 테이블로 typeorm을 적용해보겠습니다.

 

2. TypeORM(Object Relational Mapping)이란?

객체와 관계형 데이터베이스의 데이터를 자동으로 변형 및 연결하는 작업입니다.

ORM을 이용한 개발은 객체와 데이터베이스의 변형에 유연하게 사용할 수 있기 때문에 이번 프로젝트에 도입하기로 결정했습니다.

2-1. TypeORM 적용하기 위해 설치해야하는 모듈들

typeorm 라이브러리 설치

npm install @nestjs/typeorm —save

NestJS에서 TypeORM을 사용하기 위해 연동시켜주는 모듈입니다.

2-2. typeORM 애플리케이션에 연결하기

  1. typeORM 설정파일 생성

 

  2. typeORM 설정파일 작성

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

export const typeORMConfig: TypeOrmModuleOptions = {
  type: '사용하는 db',
  host: 'localhost',
  port: 3306,
  username: '설정값 넣어주기',
  password: '설정값 넣어주기',
  database: 'app',
  entities: [__dirname + '/../**/*.entity.{js,ts}'],
  synchronize: true,
};

  • entities: 엔티티를 이용해서 데이터베이스 테이블을 생성해 준다. 그래서 엔티티 파일이 어디 있는지 설정해 줍니다.
  • synchronize: true 값을 주면 애플리케이션을 다시 실행할 때 엔티티 안에서 수정된 컬럼의 길이 타입 변경값 등을 해당 테이블을 drop 한 후 다시 생성해 줍니다.

3. 루트 모듈에서 import 해준다.

app.module.ts

forRoot 안에 넣어준 설정(configuration)은 모든 Sub-Module 부수적인 모듈들에 다 적용이 됩니다.

 

3. typeORM 작성하기

저는 주문 테이블, 재료 테이블, 이 두 개의 테이블을 연결하는 연결테이블 총 3가지 테이블을 typeorm으로 작성해 보겠습니다. 해야 할 일은 주문테이블-연결테이블을 1:N으로 나타내어야 하고, 재료테이블-연결테이블을 1:N으로 나타내어야 합니다.

주문 테이블

import {
  BaseEntity,
  Column,
  CreateDateColumn,
  DeleteDateColumn,
  Entity,
  OneToMany,
  PrimaryGeneratedColumn,
  UpdateDateColumn,
} from 'typeorm';
import { Connect } from './connect.entity';

@Entity('Order')
export class Order extends BaseEntity {
  @PrimaryGeneratedColumn({ type: 'bigint' })
  id: bigint;

  @Column({ type: 'varchar', length: 200, nullable: true })
  name: string;

  @Column({ type: 'varchar', length: 10, default: 'pizza' })
  type: string;

  @Column({ type: 'text' })
  script: string;

  @OneToMany(() => Connect, (connect) => connect.order, { cascade: true })
  connects: Connect[];

  @CreateDateColumn({ type: 'datetime' })
  created_at: Date;

  @UpdateDateColumn({ type: 'datetime' })
  updated_at: Date;

  @DeleteDateColumn({ type: 'boolean', default: false })
  deleted: boolean;
}
  • Order이름을 가진 테이블의 id를 @PrimaryGeneratedColumn 값으로 주어 auto_increment가 되게 하였습니다.
  • 하나의 주문에 대해 여러 개의 재료를 가진 연결테이블을 불러오기 위해 1:N으로 구성하여 @OneToMany 데코레이션을 사용했습니다.

재료 테이블

import {
  BaseEntity,
  Column,
  CreateDateColumn,
  DeleteDateColumn,
  Entity,
  OneToMany,
  PrimaryGeneratedColumn,
  UpdateDateColumn,
} from 'typeorm';
import { Connect } from './connect.entity';

@Entity('Ingredient')
export class Ingredient extends BaseEntity {
  @PrimaryGeneratedColumn({ type: 'bigint' })
  id: number;

  @Column({ type: 'varchar' })
  name: string;

  @Column({ type: 'tinyint' })
  oven: number;

  @Column({ type: 'varchar' })
  type: string;

  @OneToMany(() => Connect, (connect) => connect.ingredient, {
    cascade: true,
  })
  connects: Connect[];

  @CreateDateColumn({ type: 'datetime' })
  created_at: Date;

  @UpdateDateColumn({ type: 'datetime' })
  updated_at: Date;

  @DeleteDateColumn({ type: 'boolean', default: false })
  deleted: boolean;
}
  • Ingredient이름을 가진 테이블의 id를 @PrimaryGeneratedColumn 값으로 주어 auto_increment가 되게 하였습니다.
  • 또한 연결테이블과 1:N 관계를 표현하기 위해 @OneToMany 데코레이터를 써서 connect의 ingredient가 외래값으로 사용된 것을 나타내었습니다.

연결테이블

import {
  BaseEntity,
  Column,
  CreateDateColumn,
  DeleteDateColumn,
  Entity,
  JoinColumn,
  ManyToOne,
  PrimaryGeneratedColumn,
  UpdateDateColumn,
} from 'typeorm';
import { Order } from './order.entity';
import { Ingredient } from './ingredient.entity';

@Entity('Connect')
export class Connect extends BaseEntity {
  @PrimaryGeneratedColumn({ type: 'bigint' })
  id: number;

  @ManyToOne(() => Order, (order) => order.connects, { onDelete: 'CASCADE' })
  @JoinColumn({ name: 'order_id' })
  order: Order;

  @ManyToOne(() => Ingredient, (Ingredient) => Ingredient.connects, {
    onDelete: 'CASCADE',
  })
  @JoinColumn({ name: 'ingredient_id' })
  ingredient: Ingredient;

  @Column({ type: 'tinyint', default: 0 })
  side: number;

  @CreateDateColumn({ type: 'datetime' })
  created_at: Date;

  @UpdateDateColumn({ type: 'datetime' })
  updated_at: Date;

  @DeleteDateColumn({ type: 'boolean', default: false })
  deleted: boolean;
}

  • @ManyToOne(() => Order, (order) => order.connects, { onDelete: 'CASCADE' }) : Connect 엔티티에서 Order 객체를 참조하고 DB에서는 Connect 테이블의 order_id라는 이림의 외래키가 생성되고 order_id는 Order테이블의 id를 참조합니다.
  • @ManyToOne(() => Ingredient, (Ingredient) => Ingredient.connects, {onDelete: 'CASCADE'}): Connect 엔티티에서 Ingredient 객체를 참조하고 DB에서는 Ingredient 테이블의 ingredient_id라는 이림의 외래키가 생성되고 ingredient_id는 Ingredient테이블의 id를 참조합니다.

이번에 DB연결관계를 정의하면서 가장 어려웠던 점은 재료-연결 테이블이 N:M이 왜 아닐까? 였습니다.

저는 재료 N개에 대하여 여러 가지로 연결될 수 있고 , 여러 개의 연결에는 여러개의 재료가 필요하기 때문에 N:M 관계로 생각했었습니다.

그럼에도 재료-연결 테이블이 1:N인 이유는 id가 1번인 채로 여러개의 ingredient_id가 못 오기 때문이었습니다.

(예를 들어, id(1)-ingredient_id(3,4,5,6) 이렇게 오는 것이 아니라 id(1)-ingredient_id(3), id(1)-ingredient_id(4), id(1)-ingredient_id(5), id(1)-ingredient_id(6) 이렇게 하나씩 대응됨)

또한 ingredient_id 관점에서 볼 때 여러 개의 재료를 동시에 보낼 수 있기 때문에(예를 들어, ingredient_id(1)- id(1) , ingredient_id(1)-id(2)) 1:N 관계였습니다.

 

결과

Order 테이블

 

Ingredient 테이블
Connect 테이블

 

다음 글로는..

다음 글로는 DB에 데이터를 주입하는 내용으로 찾아뵙겠습니다 !

긴 글 읽어주셔서 감사합니다. 다음 글에서 뵙겠습니다. 😊

728x90