NestJS๋ฅผ ๊ณต๋ถ€ํ•˜๋ฉด์„œ ๊ฐœ์ธ์ ์ธ ์šฉ๋„๋กœ ์ •๋ฆฌํ•œ ๊ธ€์ž…๋‹ˆ๋‹ค. ์ง€์ ์€ ์–ธ์ œ๋‚˜ ํ™˜์˜์ž…๋‹ˆ๋‹ค :)

5 minute read

NestJS๋ฅผ ๊ณต๋ถ€ํ•˜๋ฉด์„œ ๊ฐœ์ธ์ ์ธ ์šฉ๋„๋กœ ์ •๋ฆฌํ•œ ๊ธ€์ž…๋‹ˆ๋‹ค. ์ง€์ ์€ ์–ธ์ œ๋‚˜ ํ™˜์˜์ž…๋‹ˆ๋‹ค :)

Set-up

์‹œ์ž‘ ์ „์— ๋ช‡๊ฐ€์ง€ ์„ ํ–‰ ๊ฐœ๋…์„ ์–ธ๊ธ‰ํ•˜๊ณ  ์‹œ์ž‘ํ•˜์ž.

  • <Primary Key>: ํ•ด๋‹น ํ…Œ์ด๋ธ”์˜ ์‹๋ณ„์ž ์—ญํ• ์„ ํ•˜๋Š” ์ œ์•ฝ์กฐ๊ฑด์œผ๋กœ ํ…Œ์ด๋ธ”์— ํ•˜๋‚˜๋งŒ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Œ.
  • <Unique Key>: ํ•ด๋‹น ์ปฌ๋Ÿผ์— ์ž…๋ ฅ๋˜๋Š” ๋ฐ์ดํ„ฐ๊ฐ€ ์œ ์ผํ•จ์„ ๋ณด์žฅํ•˜๊ธฐ ์œ„ํ•œ ์ œ์•ฝ์กฐ๊ฑด์œผ๋กœํ•œ ํ…Œ์ด๋ธ”์— ์—ฌ๋Ÿฌ๊ฐœ ์„ค์ •ํ•  ์ˆ˜ ์žˆ๋‹ค. ๋‹น์—ฐํžˆ Primary Key๋Š” Unique Key์ด๊ธฐ๋„ ํ•˜๋‹ค.

๋ฌผ๋ก  ๊ฒฝ์šฐ์— ๋”ฐ๋ผ์„œ๋Š” ํ•˜๋‚˜์˜ ํ…Œ์ด๋ธ”์— ๋‘ ๊ฐœ ์ด์ƒ์˜ <Pimary Key>๋ฅผ ์„ค์ •ํ•˜๊ธฐ๋„ ํ•œ๋‹ค. ์ด ๊ฒฝ์šฐ, ๋‘ ๊ฐœ์˜ Key ๋ชจ๋‘์— ๋Œ€ํ•ด์„œ ์ค‘๋ณต์— ๋Œ€ํ•œ ๊ฒ€์‚ฌ๋ฅผ ์‹ค์‹œํ•˜๊ฒŒ ๋œ๋‹ค!


DB Relationship

์ผ๋ฐ˜์ ์œผ๋กœ DB์˜ ๊ฐ Entity๋“ค์„ ์„œ๋กœ ์™„์ „ํžˆ ๊ฒฉ๋ฆฌ๋˜์–ด ์žˆ๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ, ์–ด๋Š ์ •๋„ โ€œrelationshipโ€์„ ๊ฐ€์ง„๋‹ค. (์ด๋•Œ, ๋„์ž…๋˜๋Š” ๊ฐœ๋…์ด <Foreign Key>๋‹ค!) ์„œ๋กœ ๋‹ค๋ฅธ ๋‘ Entity๊ฐ€ ํ•จ๊ป˜ ์‚ฌ์šฉ๋˜๊ธฐ ์œ„ํ•ด์„œ๋Š” <Join>์ด๋ผ๋Š” ํ•˜๊ฒŒ ๋˜๋Š”๋ฐ, ์ด Join์„ ์ˆ˜ํ–‰ํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ๊ฐ Entity ์‚ฌ์ด์— ์ ์ ˆํ•œ <Relationship>์ด ์ •์˜๋˜์–ด ์žˆ์–ด์•ผ ํ•œ๋‹ค.

One-to-One

<One-to-One> ๊ด€๊ณ„๋Š” ์•„๋ž˜์™€ ๊ฐ™์ด ๊ธฐ์ˆ ๋œ๋‹ค.

$A$ contains only one instance of $B$, and $B$ contains only one instance of $A$.

TypeORM ๊ณต์‹ ์—์„  User์™€ Profile์˜ ์˜ˆ๋ฅผ ์ œ์‹œํ•œ๋‹ค.

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

    @Column()
    gender: string;

    @Column()
    photo: string;
}
@Entity()
export class User {
    @PrimaryGeneratedColumn()
    id: number;

    @Column()
    name: string;

    @OneToOne(() => Profile)
    @JoinColumn()
    profile: Profile;
}

๊ฐ User๋Š” โ€œ๋‹จ ํ•˜๋‚˜โ€์˜ Profile ๊ฐ์ฒด๋ฅผ ๊ฐ–๋Š”๋‹ค. ๊ทธ๋ฆฌ๊ณ  Profile ๊ฐ์ฒด๋Š” ์˜ค์ง โ€œ๋‹จ ํ•˜๋‚˜โ€์˜ User์— ๋Œ€ํ•ด์„œ๋งŒ ์—ฐ๊ด€๋œ๋‹ค. ์ฝ”๋“œ๋ฅผ ์‚ดํŽด๋ณด๋ฉด, User Entity๊ฐ€ Profile ๊ฐ์ฒด๋ฅผ ๊ฐ–๊ณ  ์žˆ์Œ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

๋˜, User Entity์— @JoinColumn()์ด๋ผ๋Š” ๋ฐ์ฝ”๊ฐ€ ๋ถ™์–ด์žˆ๋Š”๋ฐ, ์ด ๊ฒฝ์šฐ, User Table์— profileId๊ฐ€ ์ž๋™์œผ๋กœ <Foreign Key>๋กœ ์„ค์ •๋œ๋‹ค!

+-------------+--------------+----------------------------+
|                          user                           |
+-------------+--------------+----------------------------+
| id          | int(11)      | PRIMARY KEY AUTO_INCREMENT |
| name        | varchar(255) |                            |
| profileId   | int(11)      | FOREIGN KEY                |
+-------------+--------------+----------------------------+

์ฃผ์˜ํ•  ์ ์€ One-to-One ์•„๋ž˜์—์„œ๋Š” @JoinColumn()์ด ๋‹จ๋ฐฉํ–ฅ์œผ๋กœ ์„ค์ •๋œ๋‹ค๋Š” ๊ฒƒ์ด๋‹ค. ๋…ผ๋ฆฌ์ ์œผ๋กœ๋„ Target Table์„ ๊ฐ์ฒด๋กœ ๊ฐ–๋Š” ์ชฝ์—์„œ @JoinColumn() ๋ฐ์ฝ”๋ฅผ ์“ฐ๋Š” ๊ฒƒ์ด ์˜ณ๋‹ค!

์ด๋ ‡๊ฒŒ ์„ค์ •ํ–ˆ์„ ๊ฒฝ์šฐ, find()๋กœ User ์ •๋ณด๋ฅผ ๋ถˆ๋Ÿฌ์˜ฌ ๋•Œ, ์•„๋ž˜์™€ ๊ฐ™์€ FindOptions๋ฅผ ์ฃผ๋ฉด, Foreign Table์ธ Profile์˜ ์ •๋ณด๊นŒ์ง€ ํ•จ๊ป˜ ๋ถˆ๋Ÿฌ์˜ฌ ์ˆ˜ ์žˆ๋‹ค!

const users = await userRepository.find({ relations: ["profile"] });

๊ทธ ์™ธ์—๋„ Profile์—์„œ User ์ •๋ณด๋ฅผ ์ฐธ์กฐํ•  ์ˆ˜ ์žˆ๋„๋ก, <์–‘๋ฐฉํ–ฅ Bi-direction>์œผ๋กœ ์„ค์ •ํ•ด์ค„ ์ˆ˜๋„ ์žˆ๋‹ค. ์ฝ”๋“œ๋Š” TypeORM์˜ ๊ณต์‹ ๋ฌธ์„œ๋ฅผ ์ฐธ๊ณ ํ•˜๊ธธ ๋ฐ”๋ž€๋‹ค. ๐Ÿ‘‰ link

์ฃผ์˜ํ•  ์ ์€ One-to-One์œผ๋กœ ์ƒ์„ฑ๋˜๋Š” Foreign Key๋Š” <Unique Key>๋ผ๋Š” ์ ์ด๋‹ค! ๋งŒ์•ฝ Unique ์กฐ๊ฑด์„ ๋งŒ์กฑ๋˜์ง€ ์•Š๋Š” DB๋ฅผ ์„ค๊ณ„ํ•˜๊ณ  ์žˆ๋‹ค๋ฉด, One-to-One์ด ์•„๋‹Œ ์•„๋ž˜์˜ One-to-Many ๋ฐฉ์‹์„ ์จ์•ผ ํ•œ๋‹ค!

One-to-Many / Many-to-One

<One-to-Many> ๋˜๋Š” <Many-to-One> ๊ด€๊ณ„๋Š” ์•„๋ž˜์™€ ๊ฐ™์ด ๊ธฐ์ˆ ๋œ๋‹ค.

$A$ contains multiple instances of $B$, but $B$ contains only one instance of $A$.

TypeORM ๊ณต์‹ ๋ฌธ์„œ์—์„  User์™€ Photo์˜ ์˜ˆ๋ฅผ ๋“ค๊ณ  ์žˆ๋‹ค. ๊ฐ Photo๋Š” ์˜ค์ง โ€œ๋‹จ ํ•˜๋‚˜โ€์˜ User๋ฅผ ๊ฐ–์ง€๋งŒ, User๋Š” ์—ฌ๋Ÿฌ Photo๋ฅผ ๊ฐ€์งˆ ์ˆ˜ ์žˆ๋‹ค. ์ด ๊ฒฝ์šฐ, Photo๊ฐ€ Many, User๊ฐ€ One์ด ๋œ๋‹ค.

์ฝ”๋“œ๋Š” TypeORM์˜ ๊ณต์‹ ๋ฌธ์„œ๋ฅผ ์ฐธ๊ณ ํ•˜๊ธธ ๋ฐ”๋ž€๋‹ค. ๐Ÿ‘‰ link

One-to-Many / Many-to-One ์—์„œ ์ฃผ์˜ํ•  ์ ์€ โ€œ@OneToMany cannot exist without @ManyToOne.โ€๋ผ๋Š” ์ ์ด๋‹ค. ์ฆ‰, ํ•œ์ชฝ์—์„œ๋งŒ Relationship์„ ํ•„์š”๋กœ ํ•˜๋Š” ์ƒํ™ฉ์ด๋”๋ผ๋„ ๋‘ Entity ๋ชจ๋‘์— ๋Œ€ํ•ด์„œ @OneToMany, @ManyToOne์„ ์„ค์ •ํ•ด์ค˜์•ผ ํ•œ๋‹ค๋Š” ๋ง์ด๋‹ค! ์ฐธ๊ณ ๋กœ @OneToOne์—์„  ํ•œ์ชฝ์—์„œ๋งŒ ์„ค์ •ํ•ด์ค˜๋„ ๊ดœ์ฐฎ์•˜๋‹ค!

๊ทธ์™ธ์—” One-to-One๊ณผ ๊ฑฐ์˜ ๋น„์Šทํ•˜๋‹ค.

Many-to-Many

<Many-to-Many> ๊ด€๊ณ„๋Š” ์•„๋ž˜์™€ ๊ฐ™์ด ๊ธฐ์ˆ ๋œ๋‹ค.

$A$ contains multiple instances of $B$, and $B$ contains multiple instances of $A$.

TypeORM ๊ณต์‹ ๋ฌธ์„œ์—์„  Question๊ณผ Category์˜ ์˜ˆ๋ฅผ ๋“ค๊ณ  ์žˆ๋‹ค. ๊ฐ Question์€ ์—ฌ๋Ÿฌ ๊ฐœ์˜ Category๋ฅผ ๊ฐ€์งˆ ์ˆ˜ ์žˆ๋‹ค. ๋ฐ˜๋Œ€๋กœ ๊ฐ Category๋Š” ์—ฌ๋Ÿฌ ๊ฐœ์˜ Question์„ ๊ฐ€์งˆ ์ˆ˜ ์žˆ๋‹ค!

์ฝ”๋“œ๋Š” TypeORM์˜ ๊ณต์‹ ๋ฌธ์„œ๋ฅผ ์ฐธ๊ณ ํ•˜๊ธธ ๋ฐ”๋ž€๋‹ค. ๐Ÿ‘‰ link

Many-to-Many๋Š” ๊ธฐ์กด์˜ relation๊ณผ ๋‹ฌ๋ฆฌ ์ž‘๋™ ์–‘์ƒ์ด ์กฐ๊ธˆ ๋‹ค๋ฅด๋‹ค!!

Many-to-Many์—์„  @JoinTable์„ ํ†ตํ•ด ๋‘ Table์— ๋Œ€ํ•œ Foreign Key๊ฐ€ ๋‹ด๊ธด ์ƒˆ๋กœ์šด Table์ด ์ƒ์„ฑ๋œ๋‹ค!!

์‚ฌ์‹ค ๊ฐœ์ธ์ ์ธ ๊ฒฝํ—˜์œผ๋ก  Many-to-Many๋Š” ๋ญ”๊ฐ€ ์ œ์•ฝ์ด ๋งŽ์•„์„œ DB๋ฅผ ์„ค๊ณ„ํ•˜๋Š”๋ฐ์— ์ข‹์€ ์„ ํƒ์ด ์•„๋‹ˆ์—ˆ๋˜ ๊ฒƒ ๊ฐ™๋‹ค. ๋งŒ์•ฝ Many-to-Many๋ฅผ ์‚ฌ์šฉํ•˜๋‹ค๊ฐ€ ๋ง‰ํžŒ๋‹ค๋ฉด, ๊ณผ๊ฐํžˆ Many-to-Many ๋ฐฉ์‹์„ ํฌ๊ธฐํ•˜๊ณ  ์šฐํšŒํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์ฐพ์•„๋ณด๋Š” ๊ฒƒ๋„ ์ข‹์€ ์ ‘๊ทผ์ผ ๊ฒƒ ๊ฐ™๋‹ค! ๐Ÿ˜ฅ


๋งบ์Œ๋ง

์‚ฌ์‹ค ๋ชจ๋“  DB relationship์„ ์œ„์—์„œ ์†Œ๊ฐœํ•œ ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ ์—†์ด ๊ทธ๋ƒฅ ์ง์ ‘ ๊ตฌํ˜„ํ•ด๋„ ํ•  ์ˆ˜๋Š” ์žˆ๋‹ค!! ํ•˜์ง€๋งŒ, ์ด๋ ‡๊ฒŒ DB๋ฅผ ์„ค๊ณ„ํ•˜๋‹ค๋ณด๋ฉด ์–ธ์  ๊ฐ€๋Š” DB relationship์— ๋Œ€ํ•œ ๋‹ˆ์ฆˆ๊ฐ€ ํ•„์š”ํ•˜๊ฒŒ ๋˜๊ณ , ๋•Œ์— ๋”ฐ๋ผ์„œ๋Š” DB relationship์„ ์ ๊ทน ํ™œ์šฉํ•˜๋Š”๊ฒŒ ๊ตฌํ˜„์„ ๋” ๊ฐ€๋ณ๊ฒŒ ๊ฐ€์ ธ๊ฐˆ ์ˆ˜๋„ ์žˆ๋‹ค. ๐Ÿ˜Š

Categories:

Updated: