TypeORM에서의 엔티티 모델링 기법

김영훈·2024년 11월 27일

NestJS

목록 보기
4/9
post-thumbnail

📌 Entity Embedding

import {PrimaryGeneratedColumn} from "typeorm";

export class Name {
    @Column()
    first: string

    @Column()
    last: string
}


@Entity()
export class User {
    @PrimaryGeneratedColumn()
    id: string
    
    @Column(()=> Name)
    name: Name

    @Column()
    isActive: boolean
}

@Entity()
export class Employee {
    @PrimaryGeneratedColumn()
    id: string 
    
    @Column(()=> Name)
    name: Name 
    
    @Column()
    salary: number
}

생성되는 테이블 모습

USER
idintPRIMARY KEY AUTO_INCREMENT
nameFirstvarchar
nameLastvarchar
isActiveboolean

employee
idintPRIMARY KEY AUTO_INCREMENT
nameFirstvarchar
nameLastvarchar
salaryboolean

✏️ embed 된 컬럼은 class 명 + 컬럼명 순으로 생성된다

✏️실제 적용해보기

📒적용 전

@Entity()
export class Movie{
    @PrimaryGeneratedColumn()
    id: number;

    @Column()
    title: string;

    @Column()
    genre: string;

    @CreateDateColumn()
    createdAt: Date;

    @UpdateDateColumn()
    updatedAt: Date;

    @VersionColumn()
    version: number;
}

✔️기존 데이터

{
    "title": "반지의 제왕",
    "genre": "fantasy",
    "id": 1,
    "createdAt": "2024-11-27T03:17:52.708Z",
    "updatedAt": "2024-11-27T03:17:52.708Z",
    "version": 1
}

✏️ 기존 MOVIE 엔티티와 생성되는 데이터 형식

📒적용 후

import {Column, CreateDateColumn, Entity, PrimaryGeneratedColumn, UpdateDateColumn, VersionColumn} from "typeorm";

export class BaseEntity{
    @CreateDateColumn()
    createdAt: Date;

    @UpdateDateColumn()
    updatedAt: Date;

    @VersionColumn()
    version: number;
}

@Entity()
export class Movie{
    @PrimaryGeneratedColumn()
    id: number;

    @Column()
    title: string;

    @Column()
    genre: string;

    @Column(() => BaseEntity)
    base: BaseEntity;
}

✔️ 적용 후 데이터

{
    "title": "반지의 제왕",
    "genre": "fantasy",
    "id": 1,
    "base": {
        "createdAt": "2024-11-27T03:17:52.708Z",
        "updatedAt": "2024-11-27T03:17:52.708Z",
        "version": 1
    }
}

✔️ 같은 테이블이지만 객체가 들어가 있다고 인식된다.



📌Entity Inheritance

import {Column, PrimaryGeneratedColumn} from "typeorm";

export abstract class Content {
    @PrimaryGeneratedColumn()
    id: number

    @Column()
    title: string 
    
    @Column()
    description: string
}

@Entity()
export class Photo extends Content {
    @Column()
    size: string 
}

@Entity()
export class Post extends Content {
    @Column()
    viewCount: number
}

생성되는 테이블 모습

Photo
idintPRIMARY KEY AUTO_INCREMENT
titlevarchar
descriptionvarchar
sizeboolean

Post
idintPRIMARY KEY AUTO_INCREMENT
titlevarchar
descriptionvarchar
viewCountnumber

실제 적용해보기

import {Column, CreateDateColumn, Entity, PrimaryGeneratedColumn, UpdateDateColumn, VersionColumn} from "typeorm";

export abstract class BaseEntity{
    @CreateDateColumn()
    createdAt: Date;

    @UpdateDateColumn()
    updatedAt: Date;

    @VersionColumn()
    version: number;
}

@Entity()
export class Movie extends BaseEntity{
    @PrimaryGeneratedColumn()
    id: number;

    @Column()
    title: string;

    @Column()
    genre: string;
}

생성되는 데이터의 모습

{
    "title": "반지의 제왕",
    "genre": "fantasy",
    "createdAt": "2024-11-27T03:25:18.406Z",
    "updatedAt": "2024-11-27T03:25:18.406Z",
    "version": 1,
    "id": 1
}


📌Single Table Inheritance

import {Entity, PrimaryGeneratedColumn} from "typeorm";

@Entity()
@TableInheritance({
    column: {
        type: "varchar",
        name: "type"
    }
})
export class Content {
    @PrimaryGeneratedColumn
    id: number 
    
    @Column()
    title: string 
    
    @Column()
    description: string 
}

@ChildEntity()
export class Photo extends Content {
    @Column()
    size: string 
}

@ChildEntity()
export class Post extends Content {
    @Column()
    viewCount: number
}

생성되는 테이블 모습

Content
idintPRIMARY KEY AUTO_INCREMENT
typevarchar
titlevarchar
descriptionvarchar
sizevarcharNull
viewCountvarcharNull

실제 적용해보기

import {ChildEntity, Column, CreateDateColumn, Entity, PrimaryGeneratedColumn, TableInheritance, UpdateDateColumn, VersionColumn} from "typeorm";

export abstract class BaseEntity{
    @CreateDateColumn()
    createdAt: Date;

    @UpdateDateColumn()
    updatedAt: Date;

    @VersionColumn()
    version: number;
}

// movie / series -> Content
// runtime (영화 상영시간) / seriesCount (몇개 부작인지)

@Entity()
@TableInheritance({
    column: {
        type: 'varchar',
        name: 'type'
    }
})
export class Content extends BaseEntity{
    @PrimaryGeneratedColumn()
    id: number;

    @Column()
    title: string;

    @Column()
    genre: string;
}

@ChildEntity()
export class Movie extends Content{
    @Column()
    runtime: number;
}

@ChildEntity()
export class Series extends Content{
    @Column()
    seriesCount: number;
}

✏️ app.moduleentity 추가

// app.module
import { Module } from '@nestjs/common';
import { MovieModule } from './movie/movie.module';
import {TypeOrmModule} from "@nestjs/typeorm";
import {ConfigModule, ConfigService} from "@nestjs/config";
import * as Joi from "joi";
import {Content, Movie, Series} from "./movie/entity/movie.entity";

@Module({
  imports: [
      ConfigModule.forRoot({
          isGlobal: true, // ConfigModule 에 정의한 환경변수들을 어떤 모듈에서든 사용가능하게 함
          validationSchema: Joi.object({
              ENV: Joi.string().valid('dev', 'prod').required(),
              DB_TYPE: Joi.string().valid('postgres').required(),
              DB_HOST: Joi.string().required(),
              DB_PORT: Joi.number().required(),
              DB_USERNAME: Joi.string().required(),
              DB_PASSWORD: Joi.string().required(),
              DB_DATABASE: Joi.string().required(),
          }),
      }),
      TypeOrmModule.forRootAsync({
          useFactory: (configService: ConfigService) => ({
              type: configService.get<string>("DB_TYPE") as "postgres",
              host: configService.get<string>("DB_HOST"),
              port: configService.get<number>("DB_PORT"),
              username: configService.get<string>("DB_USERNAME"),
              password: configService.get<string>("DB_PASSWORD"),
              database: configService.get<string>("DB_DATABASE"),
              entities: [
                  Movie, Series, Content
              ],
              synchronize: true,
          }),
          inject: [ConfigService]
      }),
      MovieModule
  ],
})
export class AppModule {}

✏️ Movie.module 에도 추가.

import { Module } from '@nestjs/common';
import { MovieService } from './movie.service';
import { MovieController } from './movie.controller';
import {TypeOrmModule} from "@nestjs/typeorm";
import {Movie, Series} from "./entity/movie.entity";

@Module({
  imports:[TypeOrmModule.forFeature([
      Movie, Series
  ])],
  controllers: [MovieController],
  providers: [MovieService],
})
export class MovieModule {}

테스트를 위한ServiceController 수정

// `movie.service`
async create(createMovieDto: CreateMovieDto) {
    return await this.movieRepository.save({
        ...createMovieDto,
        runtime: 100
    });
}


// 테스트용
async createSeries(createMovieDto: CreateMovieDto){
    return await this.seriesRepository.save({
        ...createMovieDto,
        seriesCount: 100,
    })
}

// movie.controller
@Post()
create(@Body() body: CreateMovieDto) {
    return this.movieService.create(body);
}

@Post('series')
createSeries(@Body() body: CreateMovieDto) {
    return this.movieService.createSeries(body);
}

데이터 생성결과

{
    "title": "반지의 제왕",
    "genre": "fantasy",
    "runtime": 100,
    "createdAt": "2024-11-27T03:43:28.490Z",
    "updatedAt": "2024-11-27T03:43:28.490Z",
    "version": 1,
    "id": 2
}

✏️ movie 생성

{
    "title": "파괴의 제왕",
    "genre": "fantasy",
    "seriesCount": 100,
    "createdAt": "2024-11-27T03:43:22.157Z",
    "updatedAt": "2024-11-27T03:43:22.157Z",
    "version": 1,
    "id": 1
}

✏️ Series 생성

실제 데이터베이스 확인

         List of relations
 Schema |  Name   | Type  | Owner
--------+---------+-------+--------
 public | content | table | myuser
(1 row)

✏️ content 라는 테이블 하나만 존재함.

테이블 조회 모습

  createdAt          |         updatedAt          | version | id |    title    |  genre  | runtime | seriesCount |  type
----------------------------+----------------------------+---------+----+-------------+---------+---------+-------------+--------
 2024-11-27 12:43:22.157592 | 2024-11-27 12:43:22.157592 |       1 |  1 | 파괴의 제왕 | fantasy |         |         100 | Series
 2024-11-27 12:43:28.490796 | 2024-11-27 12:43:28.490796 |       1 |  2 | 반지의 제왕 | fantasy |     100 |             | Movie
(2 rows)

✏️ type 컬럼에 Movie | Series 를 구분할 수 있도록 값이 들어가 있고, runtimeseriesCountnullable 로 설정된 모습.


📌정리

✏️ 임베디드 엔티티
반복적으로 사용되는 속성을 재사용하려는 경우 사용되며,
데이터베이스 테이블에 독립적인 테이블로 생성되지 않고, 사용하는 엔티티의 컬럼들로 직접 포함됨.

✏️ 상속 기반 엔티티(Entity Inheritance)
공통 속성이 있지만, 개별적인 테이블로 데이터를 관리하려는 경우 사용되며,
추상 클래스로 설정되어 독립적인 테이블로 생성되지 않는다.
상속받은 엔티티 들은 각각 별도의 테이블로 생성되며, 추상 클래스의 모든 속성을 상속받는다.

✏️ 단일 테이블 상속(Single Table Inheritance)
공통 속성이 있고, 데이터를 하나의 테이블에서 관리하려는 경우 사용한다.
테이블 하나만 생성되며, 각 행은 type 컬럼에 의해 구분된다.

0개의 댓글