본문 바로가기

FrontEnd

[TypeORM] 관계 심화

반응형

라이프사이클을 같이 관리하고 싶다면 cascade 옵션을 ["insert","update","delete"] 로 지정한다.

(부모가 하나일때!)

onDelete는 @ManyToOne 쪽에 설정한다. (자식이 부모따라 가는 매커니즘)

# 1대1 관계

일대일 관계는 A가 B의 인스턴스를 하나만 포함하고 B가 A의 인스턴스를 하나만 포함하는 관계입니다.

예를 들어 사용자 및 프로필 엔터티를 살펴보겠습니다.

사용자는 단일 프로필만 가질 수 있으며 단일 프로필은 단일 사용자만 소유합니다.

1대1 관계

FK 쪽에만 관계 지정하기

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

@Entity()
export class Profile {

    @PrimaryGeneratedColumn()
    id: number;

    @Column()
    gender: string;

    @Column()
    photo: string;

}
// @JoinColumn을 설정한 쪽의 테이블에는 대상 엔터티 테이블에 대한 "관계 ID"와 외래 키가 포함됩니다.
import {Entity, PrimaryGeneratedColumn, Column, OneToOne, JoinColumn} from "typeorm";
import {Profile} from "./Profile";

@Entity()
export class User {

    @PrimaryGeneratedColumn()
    id: number;

    @Column()
    name: string;

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

}

1대1 관계 저장하기

const profile = new Profile();
profile.gender = "male";
profile.photo = "me.jpg";
// cascade 설정이 되어있으면 profile은 저장하지 않아도 됨.
await connection.manager.save(profile);

const user = new User();
user.name = 'Joe Smith';
user.profile = profile;
// cascade 설정이 되어있으면 profile은 저장하지 않아도 됨.
await connection.manager.save(user);

1대1 관계 불러오기

// eager 없으면 relation 옵션 줘야 함
const userRepository = connection.getRepository(User);
const users = await userRepository.find({ relations: ["profile"] });

// 쿼리빌더
const users = await connection
    .getRepository(User)
    .createQueryBuilder("user")
    .leftJoinAndSelect("user.profile", "profile")
    .getMany();

양방향 참조 지정

import {Entity, PrimaryGeneratedColumn, Column, OneToOne} from "typeorm";
import {User} from "./User";

@Entity()
export class Profile {

    @PrimaryGeneratedColumn()
    id: number;

    @Column()
    gender: string;

    @Column()
    photo: string;

    @OneToOne(() => User, user => user.profile) // specify inverse side as a second parameter
    user: User;

}

// ** @JoinColumn은 관계의 한쪽에만 있어야 합니다. 즉, 외래 키를 소유할 테이블에 있어야 합니다. **
import {Entity, PrimaryGeneratedColumn, Column, OneToOne, JoinColumn} from "typeorm";
import {Profile} from "./Profile";

@Entity()
export class User {

    @PrimaryGeneratedColumn()
    id: number;

    @Column()
    name: string;

    @OneToOne(() => Profile, profile => profile.user) // specify inverse side as a second parameter
    @JoinColumn()
    profile: Profile;

}
// 역방향 조인이 가능하다.
const profiles = await connection
    .getRepository(Profile)
    .createQueryBuilder("profile")
    .leftJoinAndSelect("profile.user", "user")
    .getMany();
    
// 근데 꼭 릴레이션 설정이 되어있어야 조인할수 있는건 아님.
const user = await createQueryBuilder("user")
    .leftJoinAndSelect(Photo, "photo", "photo.userId = user.id")
    .getMany();

 

# 다대일 , 일대다

다대일/일대다 관계는 A가 B의 여러 인스턴스를 포함하지만 B는 A의 인스턴스를 하나만 포함하는 관계입니다.

예를 들어 User 및 Photo 엔터티를 살펴보겠습니다.

사용자는 여러 장의 사진을 가질 수 있지만 각 사진은 한 명의 사용자만 소유합니다.

import {Entity, PrimaryGeneratedColumn, Column, ManyToOne} from "typeorm";
import {User} from "./User";

@Entity()
export class Photo {

    @PrimaryGeneratedColumn()
    id: number;

    @Column()
    url: string;
	// 사진은 여러개 있을 수 있지만 사용자는 반드시 하나.
    // 사용자의 user.photos필드에 Photo[]
    // @JoinColumn({ referencedColumnName: "id" ,name:userId})가 디폴트임
    @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;
	// 사진은 여러개 있을 수 있지만 사용자는 반드시 하나.
    // photo의 photo.user 필드에  User
    // @JoinColumn({ referencedColumnName: "id" ,name:photoId})가 디폴트임
    @OneToMany(() => Photo, photo => photo.user)
    photos: Photo[];

}
    • @ManyToOne / @OneToMany 관계에서는 @JoinColumn을 생략할 수 있습니다.
      • @ManyToOne을 설정한 엔터티에 FK와 relationId가 생성됩니다.
    • @OneToMany를 사용하려면 @ManyToOne이 필요합니다.
    • @ManyToOne 관계에만 관심이 있는 경우 @OneToMany 설정은 생략해도 됨

해당 관계 저장 방법

// 사진을 먼저 저장하고 부모에 참조관계 만들어 저장.
const photo1 = new Photo();
photo1.url = "me.jpg";
await connection.manager.save(photo1);

const photo2 = new Photo();
photo2.url = "me-and-bears.jpg";
await connection.manager.save(photo2);

const user = new User();
user.name = "John";
user.photos = [photo1, photo2];
// cascade 옵션 : from 부모 to 자식. 부모 하나만 저장하면 됨.
await connection.manager.save(user);

// 사용자 만들고 photo에 참조관계 만들어 저장
const user = new User();
user.name = "Leo";
await connection.manager.save(user);

const photo1 = new Photo();
photo1.url = "me.jpg";
photo1.user = user;
await connection.manager.save(photo1);

const photo2 = new Photo();
photo2.url = "me-and-bears.jpg";
photo2.user = user;
await connection.manager.save(photo2);


// 쿼리하기

const userRepository = connection.getRepository(User);
const users = await userRepository.find({ relations: ["photos"] });

// or from inverse side

const photoRepository = connection.getRepository(Photo);
const photos = await photoRepository.find({ relations: ["user"] });


const users = await connection
    .getRepository(User)
    .createQueryBuilder("user")
    .leftJoinAndSelect("user.photos", "photo")
    .getMany();

// or from inverse side

const photos = await connection
    .getRepository(Photo)
    .createQueryBuilder("photo")
    .leftJoinAndSelect("photo.user", "user")
    .getMany();

 

# 다대다 관계

사실 다대다 관계는 테이블로 분리할 수 있다.

김영한님 께서 말씀하시길...

 

  • 편리해 보이지만 실무에서 사용하면 안된다.
  • 개발하다 보면, 연결 테이블이 단순히 연결만 하고 끝나지 않는다. 조인 테이블 자체에 주문시간, 수량 같은 추가 데이터가 많이 들어갈 수 있다.
  • 하지만, 매핑 정보만 넣는 것이 가능하고, 추가 정보를 넣는 것 자체가 불가능하다.
  • 그리고 중간 테이블이 숨겨져 있기 때문에 예상하지 못하는 쿼리들이 나간다.
  • 이런 문제점들 때문에 실무에서는 안쓰는게 맞다고 본다.

출처: https://ict-nroo.tistory.com/127 [개발자의 기록습관]

 

나중에 추가 학습하도록 한다 (TBD)

 

반응형