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 공식 에선 UserProfile의 예를 제시한다.

@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 공식 문서에선 UserPhoto의 예를 들고 있다. 각 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 공식 문서에선 QuestionCategory의 예를 들고 있다. 각 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: