DB, ORM/🧊 typeORM

조인 없이 관계 테이블의 fk 조회하기 + @RelationId

DarrenKwonDev 2020. 11. 10. 12:32

조인 없이 그걸 어떻게 해? 라는 의문이 들지만 typeORM에서 구현해 놓았으므로 사용할 수 있습니다.

 

예를 들어 place라는 엔티티가 user와 relationship이 형성되어있다고 가정합시다.

relationship이 설정된 후 관련 테이블을 가져오는 방식은 아래와 같이 Place 엔티티 중에서 관계성을 가진 컬럼을 클래스 메서드의 option 부분에의 realtions 값으로 넘겨주면 됩니다.

자세한 건 공식 문서 (typeorm.io/#/relations-faq/how-to-load-relations-in-entities) 를 참고하면 됩니다.

 

const place = await Place.findOne({ id: args.placeId }, {relations: ["user"]});

 

* 참고로, 복잡한 조인이 요구될 때는 QueryBuilder를 사용해야 합니다. 이 부분은 추후에 사용할 일이 생기면 다뤄보도로하겠습니다.

 

 

그런데 문제는, 관계성이 있는 테이블의 id만 필요할 때가 있는데 이 경우 위와 같이 한다면 join이 되어 전체를 다 불러오게 된다는 겁니다. 이 경우에는 typeORM에서는 아래와 같이 [관계된 컬럼id] 꼴로 적어 id만을 가져올 수 있습니다.

@Column({ nullable: true })
userId: number;

@ManyToOne((type) => User, (user) => user.places)
user: User;

너무 간략하므로 공식 문서(typeorm.io/#/relations-faq/how-to-use-relation-id-without-joining-relation)의 예시를 들어 천천히 살펴보겠습니다.

 

아래와 같이 User와 Profile은 1:1 관계가 설정된 엔티티입니다.

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

@Entity()
export class Profile {

    @PrimaryGeneratedColumn()
    id: number;

    @Column()
    gender: string;

    @Column()
    photo: string;

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

@Entity()
export class User {

    @PrimaryGeneratedColumn()
    id: number;

    @Column()
    name: string;

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

}

 

이 때 join 없이 단순히 User 엔티티를 찾아 불러온다면 아래와 같은 인스턴스를 받게 됩니다.

User {
  id: 1,
  name: "Umed"
}

 

그러나 조인없이 User 엔티티를 불러오면서 동시에 Profile 엔티티의 식별자인 id를 불러오고 싶다면 User 부분에 Profile와 연결된 컬럼 이름인 'profile'에 'Id'를 붙인 profileId라는 이름의 컬럼을 만들어주면 됩니다.

export class User {

    @PrimaryGeneratedColumn()
    id: number;

    @Column()
    name: string;

    @Column({ nullable: true })
    profileId: number;

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

}

 

이제 User 인스턴스를 불러올 때면 다음과 같이 profileId를 받을 수 있습니다. 조인 없이 불러 왔으므로 추후 필요해질 때만 profileId를 이용해 Profile 테이블을 조회하면 됩니다.

User {
  id: 1,
  name: "Umed",
  profileId: 1
}

 

 

이제 실제적으로 어떻게 다른 차이를 보이는 지 확인해봅시다. 위의 예시는 아니지만, 간단히 설명하자면 Ride는 passenger와 driver라는 컬럼으로 User 테이블과 relationship을 가지고 있습니다.

 

Join하는 방식으로 가져온 후 ride를 출력해보겠습니다. 

const ride = await Ride.findOne({ id: args.rideId }, { relations: ["passenger", "driver"] });
if (ride) console.log(ride)

 

출력 결과 다음과 같이 Join된 상태를 보실 수 있습니다.

Ride {
  id: 7,
  ... 중략
  passenger: User {
    id: 1,
    ... 중략
  },
  driver: null // 값이 없어 join하지도 않음
}

 

그러나 relationship된 부분의 fk만을 확인하기위해 relationship 옵션을 달지 않은 채로 엔티티를 다음과 같이 수정하고, 호출해보록하겠습니다.

@Column({ nullable: true })
passengerId: number;

@ManyToOne((type) => User, (user) => user.ridesAsPassenger)
passenger: User;
const ride = await Ride.findOne({ id: args.rideId });
console.log(ride);
Ride {
  id: 7,
  passengerId: 1, // passengerId의 FK만 준다.
}

 

 

@RelationId

github.com/typeorm/typeorm/blob/master/docs/decorator-reference.md#relationid

위와 같은 방법으로 사용해도 되지만 좀 더 확실하게 위 관계를 명시하는 방법은 @RelationId 데코레이터를 사용하는 것입니다.

 

되게 쉽죠~

@Entity()
export class Post {

    @ManyToOne(type => Category)
    category: Category;

    @RelationId((post: Post) => post.category) // you need to specify target relation
    categoryId: number;

}