본문 바로가기
프로그래밍/TypeORM

[TypeORM] Many-to-one / one-to-many relations

by YuminK 2023. 9. 4.

https://orkhan.gitbook.io/typeorm/docs/many-to-one-one-to-many-relations

 

예시를 보자 user는 다수의 photos를 가질 수 있다. 하지만 각 포토는 하나의 싱글 유저에게 귀속된다.

 

import { Entity, PrimaryGeneratedColumn, Column, ManyToOne } from "typeorm"

import { User } from "./User"

 

@Entity()

export class Photo {

    @PrimaryGeneratedColumn()

    id: number

 

    @Column()

    url: string

 

    @ManyToOne(() => User, (user) => user.photos)

    user: User

}

 

import { Entity, PrimaryGeneratedColumn, Column, OneToMany } from "typeorm"

import { Photo } from "./Photo"

 

@Entity()

export class User {

    @PrimaryGeneratedColumn()

    id: number

 

    @Column()

    name: string

 

    @OneToMany(() => Photo, (photo) => photo.user)

    photos: Photo[]

}

 

photos에 @OneToMany를 추가했다.

@JoinColumn을 @ManyToOne과 @OneToMany에서 생략 가능하다. (Many쪽에 붙는다)

@OneToMany는 @ManyToOne 없이 존재할 수 없다. 

@OneToMany를 사용하려면 @ManyToOne은 필수적이다.

하지만 반대로는 필수적이지 않다. (@OneToMany 없이 @ManyToOne을 사용할 수 있다.)

 

// 개인적으로는 명시적으로 어노테이션을 붙여주는 것이 나은 것 같다. 

 

@ManyToOne을 설정할 때, 그 엔티티는 relation id를 외래키로 가지게 된다.

 

예시로 생성되는 tables는 이렇다.

+-------------+--------------+----------------------------+

|                         photo                           |

+-------------+--------------+----------------------------+

| id             | int(11)      | PRIMARY KEY AUTO_INCREMENT |

| url            | varchar(255) |                            |

| userId      | int(11)      | FOREIGN KEY        |

+-------------+--------------+----------------------------+

 

+-------------+--------------+----------------------------+

|                          user                           |

+-------------+--------------+----------------------------+

| id              | int(11)      | PRIMARY KEY AUTO_INCREMENT |

| name        | varchar(255) |                            |

+-------------+--------------+----------------------------+

 

저장하는 방법 1

const photo1 = new Photo()

photo1.url = "me.jpg"

await dataSource.manager.save(photo1)

 

const photo2 = new Photo()

photo2.url = "me-and-bears.jpg"

await dataSource.manager.save(photo2)

 

const user = new User()

user.name = "John"

user.photos = [photo1, photo2]

await dataSource.manager.save(user)

 

저장하는 방법 2

const user = new User()

user.name = "Leo"

await dataSource.manager.save(user)

 

const photo1 = new Photo()

photo1.url = "me.jpg"

photo1.user = user

await dataSource.manager.save(photo1)

 

const photo2 = new Photo()

photo2.url = "me-and-bears.jpg"

photo2.user = user

await dataSource.manager.save(photo2)

 

// 2번 방식이 더 직관적이다. (3번)

// 1번 방식의 경우 JPA에서는 Photo쪽에 userId 값이 들어가지 않을 것이다. (TypeORM에서 JoinColumn이 없는 테이블에서 값을 넣고 영속했을 경우, update 쿼리가 날라가는지 확인이 필요할 듯 싶다. 예상은 쿼리 5번.) 

 

캐스캐이드 옵션을 사용하는 경우에 한번의 save 콜을 통해 저장할 수 있다. 

// User의 photos에 cascade를 걸고 리스트에 넣어둔 다음에 user를 저장하는 방식

 

유저 정보를 포토 정보와 같이 가져오기 위해서 FindOptions의 relation 속성을 명시해야 한다. 

const userRepository = dataSource.getRepository(User)

const users = await userRepository.find({

    relations: {

        photos: true,

    },

})

 

// or from inverse side

 

const photoRepository = dataSource.getRepository(Photo)

const photos = await photoRepository.find({

    relations: {

        user: true,

    },

})

 

또는 QueryBuilder를 사용하는 방법도 있다.

const users = await dataSource

    .getRepository(User)

    .createQueryBuilder("user")

    .leftJoinAndSelect("user.photos", "photo")

    .getMany()

 

// or from inverse side

 

const photos = await dataSource

    .getRepository(Photo)

    .createQueryBuilder("photo")

    .leftJoinAndSelect("photo.user", "user")

    .getMany()

 

eager로딩이 설정되어 있는 경우에, 구체적으로 find 메소드에서 명시적으로 relation을 처리해주지 않아도 된다. (자동으로 로드해주니까)

다만, QueryBuilder를 사용하는 경우에는 eager 옵션이 비활성화된다. 따라서 leftJoinAndSelect를 사용해야 한다.

 

// TypeORM에서는 기본적으로 eager와 lazy 로딩 방식을 기본 값으로 처리하지 않는다.

// 따라서 특정 테이블과 조인된 데이터를 가지고 오고 싶은 경우에 QueryBuilder방식 혹은 find함수에 findOption을 설정하는 방식으로 처리해야 한다. 

 

// N+1의 문제 가능성을 없애는 측면에서 TypeORM처럼 기본값이 아무것도 아닌 게 깔끔한 것 같다.

'프로그래밍 > TypeORM' 카테고리의 다른 글

[TypeORM] Using Validation  (0) 2023.09.04
[TypeORM] Relations  (0) 2023.09.04
[TypeORM] One-to-one relations  (0) 2023.09.03
[TypeORM] Find Options - Advanced options  (0) 2023.09.02
[TypeORM] Find Options - Basic Options  (0) 2023.09.02

댓글