최근 회사 프로젝트의 대시보드에 그래프를 출력하려다 보니, 여러 테이블들을 조인해서 들고와야 할 데이터가 생겨 쿼리를 짰다.
하지만, typeorm에서 .query메소드로 쿼리문을 적으니, 가독성이 너무 좋지 않아, createQueryBuilder메소드를 사용하기로 헀다.
그러다 체크한 entity들... 관계설정이 제대로 하나도 되어 있지 않았다.
typeORM을 이용하여 테이블간 관계 설정을 어떻게 할 수 있는지 알아보자.(feat. NestJS)
typeORM 공식홈페이지를 참조 하였다.
One-to-one relations
1대 1관계는 A가 B의 인스턴스 하나만 포함하고, B 역시 A의 인스턴스 하나만을 포함하는 관계를 말한다.
User 와 Profile 이라는 엔터티가 있다고 가정해보자. 각 user는 하나의 profile을 가지게 되고, profile 역시 하나의 user 에게만 대응이 된다. 이렇듯 1:1 관계가 되는 것을 One-to-one relations 라고 한다.
코드 예시를 보면서 이해해 보자.
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(() => Profile)
@JoinColumn()
profile: Profile
}
@OneToOne
User enitity에 OneToOne을 적용했다. 이때, OneToOne의 인자로 연결하고자 하는 Entity를 리턴하는 함수를 넣어주자.
이를 통해, 연결 대상 entity가 누구인지 알려줄 수 있다.
참고로, 검색을 하다보면 @OnetoOne((type) => Profile) 과 같이 사용하는 걸 매우 흔히 볼 수 있는데,
여기서 type이 다른 뜻이 있는 것은 아니고, typeORM 공식홈페이지에서 사용하는 단순한 convention이다.
@JoinColumn ( 참고링크 )
JoinColumn은 relation의 어떤 쪽이 join column(foreign key)을 가질 것인지를 명시하고, join column을 생성한다.
그렇기 때문에 해당 데코레이터는 반드시 한쪽의 테이블(관게를 소유하는 쪽)에만 적용되어야 하는 항목이다.
(즉, User에서 적용했다면, Profile에서는 적용하면 안된다는 뜻이다.)
Relation의 양면
Relation에는 owning-side와 inverse-side가 존재하게 된다.
여기에서 owning-side는 foreign key를 테이블에 가지게 되는 쪽을 의미하고, inverse-side는 그 반대 쪽를 의미한다.
앞서, JoinColumn은 관계를 소유하고 있는 쪽(owning-side)에 명시되어야 한다고 하였다.
즉, foreign key를 소유하게 할 entity에 JoinColumn을 적으면 된다 는 뜻이다.
앞서 말한 것처럼, @JoinColumn는 자동적으로 joincolumn(foreign key)를 생성하는데, 이때의 column의 name과 referencedColumnName을 customize 할 수 있다.
@JoinColumn 사용법
@ManyToOne((type) => Category)
@JoinColumn({ name: "cat_id", referencedColumnName: "name"})
category: Category
- name은 새로 생성될 joinColumn의 칼럼명,
- referencedColumnName은 해당 joinColumn이 바라보고 있을(refer) 칼럼을 의미한다.
위에서는 Category.name 이 될 것이다. - 만약, 해당 entity에 존재하는 이름의 column을 JoinColumn의 name으로 주게 되면, 새로운 칼럼이 생기지 않고 기존의 칼럼이 JoinColumn이 되게 된다.
@JoinColumn default 값
name: property + 참조칼럼명
referencedColumnName(참조칼럼명): 타켓 테이블의 primary key(e.g. id)
e.g)
@ManyToOne((type) => Category)
@JoinColumn()
category: Category
위의 코드는 categoryid 라는 칼럼을 생성하게 된다.(Category테이블의 primary키가 id라는 가정)
양방향 관계(bi-directional) 만들기
Relations는 uni-directional(단방향) and bi-directional(양방향) relation이 존재한다.
우리가 방금 만든 relation은 단방향 relation이다. User(owning-side) 쪽에서는 관계가 있음을 알 수 있지만,
Profile(inverse-side) 쪽에서는 어떠한 관계가 존재하는지 알 수 없게 된다. 이런 경우, profile쪽에서 user쪽으로의 접근이 힘들게 된다.
위의 단뱡향 관계를 양방향 관계로 만들어 보자!
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
//15번 라인의 (user) => user.profile의 user와 16번 라인의 user: User의 user는 서로 다르다!
}
1. @OneToOne 의 두 번째 인자로 반대편의 어떤 property와 relation이 생겨야 하는지를 함수형태로 넘겨준다.
user.profile에서, profile은 User entity에서 관계를 설정해주며 만든 property profile을 의미한다. (profile: Profile 에서의 profile)
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
}
2. owning-side에서도 @OneToOne의 두 번째 인자로 inverse side의 어떤 property와 관계를 가지는지 명시해주자.
이로써 단방향 relation을 양방향 relation으로 변경하는 작업이 끝났다.
이렇게 만든 양방향 relation에서는 owning side, inverse side 상관 없이 모두 QueryBuilder를 사용 할 수 있게 된다.
const profiles = await dataSource
.getRepository(Profile)
.createQueryBuilder("profile")
.leftJoinAndSelect("profile.user", "user")
.getMany()
Many-to-one / one-to-many relations
Many-to-one / one-to-many relations 은 User와 Photo 같은 관계이다.
하나의 user가 여러개의 photo를 가질 수 있지만, 하나의 photo는 하나의 user 밖에 가지지 못한다.
어떻게 설정 할 수 있는지 바로 코드로 알아보자.
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[]
}
위 코드에서는 @ManyToOne / @OneToMany 을 통해 관계를 정하고 있다.
주의할 점은 @ManyToOne는 혼자 사용가능하지만, @OneToMany는 혼자 사용이 되지 않고, @ManyToOne와 세트로 사용되어야 한다는 점이다. 또한, @JoinColumn이 생략 가능하다.
'TypeORM' 카테고리의 다른 글
[TypeORM] bulk insert (한번에 여러 칼럼 udpate) (0) | 2022.09.26 |
---|---|
TypeORM 관계 설정하기(ManyToOne, OneToMany) (1) | 2022.06.03 |
entity 와 db table 비교하여 migration 파일 생성 (feat. nestjs, mysql, typeorm) (0) | 2022.05.25 |
TypeORM - 기초를 탄탄하게 (0) | 2022.05.02 |