릴레이션으로 관련 엔티티의 연결을 쉽게 할 수 있다.
cascade가 true이면 save를 호출하지 않아도 관련 객체가 저장되므로 주의
@Entity()
export class Question {
@PrimaryGeneratedColumn()
id: number
@Column()
title: string
@Column()
text: string
@ManyToMany((type) => Category, (category) => category.questions, {
cascade: true,
})
@JoinTable()
categories: Category[]
}
다른 열에 대한 참조(외래키를 사용)
기본적으로 관련 엔터티의 기본 열을 참조함.
many-to-many 접합 테이블 조인 열을 설명함.
접합 테이블: TypeORM에서 자동으로 생성된 특별한 별도 테이블
A가 오직 하나의 B 인스턴스를 포함하고,
B오 오직 하나의 A 인스턴스를 포함하는 관계
대표적으로 User와 Profile 관계
@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가 owerner side인데 profile이 owener side인 것이 더 확장성 있지 않을까..? 생각함.

OneToOne 관계는 이렇게 불러올 수 있음.
const users = await dataSource.getRepository(User).find({
relations: {
profile: true,
},
})
위 ORM은 다음 형식으로 SQL 형태로 변환될 것으로 추정
SELECT u.*, p.*
FROM User u
LEFT JOIN Profile p ON u.profileId = p.id
단방향은 한쪽에만 관계 데코레이터가 있는 관계
양방향은 관계의 양쪽에 있는 데코레이터와의 관계
@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
}
@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
}
어떤 Profile 엔티티의 속성(user)이 이 관계에 연결되는지를 나타냅니다. 즉, 각 Profile은 하나의 User와 연결됩니다.
두 번째 매개변수는 "역방향(referencing) 관계"를 정의하는 함수. 이 매개변수는 관계의 "주인(owner)" 쪽이 아닌 다른 쪽에서 이 관계를 어떻게 참조하는지를 설명합니다.
A가 B의 여러 인스턴스를 포함
하지만 B는 A 인스턴스 하나만 포함
예를 들어 User와 Photo
User 인스턴스는 여러 Photo 인스턴스를 가질 수 있지만,
Photo 인스턴스는 하나의 User 인스턴스만 가질 수 있다.
@Entity()
export class Photo {
@PrimaryGeneratedColumn()
id: number
@Column()
url: string
@ManyToOne(() => User, (user) => user.photos)
user: User
}
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number
@Column()
name: string
@OneToMany(() => Photo, (photo) => photo.user)
photos: Photo[]
}
@OneToMany cannot exist without @ManyToOne.
If you only care about the @ManyToOne relationship, you can define it without having @OneToMany on the related entity.
A인스턴스가 다수의 B인스턴스를 포함할 수 있고,
B인스턴스도 다수의 A인스턴스를 포함할 수 있는 관계
대표적으로 질문과 카테고리를 들 수 있음.
하나의 질문은 다수의 카테고리를 가질 수 있고,
하나의 카테고리는 다수의 질문을 가질 수 있음.
이건 단방향 관계
@Entity()
export class Category {
@PrimaryGeneratedColumn()
id: number
@Column()
name: string
}
@Entity()
export class Question {
@PrimaryGeneratedColumn()
id: number
@Column()
title: string
@Column()
text: string
@ManyToMany(() => Category)
@JoinTable()
categories: Category[]
}
양방향 관계
@Entity()
export class Category {
@PrimaryGeneratedColumn()
id: number
@Column()
name: string
@ManyToMany(() => Question, (question) => question.categories)
questions: Question[]
}
@Entity()
export class Question {
@PrimaryGeneratedColumn()
id: number
@Column()
title: string
@Column()
text: string
@ManyToMany(() => Category, (category) => category.questions)
@JoinTable()
categories: Category[]
}
// eager
@ManyToMany((type) => Question, (question) => question.categories)
questions: Question[]
// lazy
@ManyToMany((type) => Question, (question) => question.categories)
questions: Promise<Question[]>
lazy는 Question 엔티티와의 관계가 실제로 필요할 때까지 데이터베이스에서 로딩을 지연시키고, 해당 데이터에 접근하는 순간 데이터를 가져오는 구조입니다.
레이지 로딩의 단점
N+1 문제:
레이지 로딩을 사용하면 각 엔티티에 대해 별도의 쿼리가 발생할 수 있습니다. 예를 들어, 여러 Category에 속하는 각각의 Question을 로딩할 때, 각 Question에 대해 별도의 데이터베이스 쿼리가 실행될 수 있으며, 이는 성능 저하를 초래할 수 있습니다.
JS you have to use promises if you want to have lazy-loaded relations. This is non-standard technique and considered experimental in TypeORM.
N+1 문제는 하나의 데이터를 가져온 후, 관련된 각 데이터를 개별적으로 추가로 조회할 때 발생
한 번의 쿼리로 해결 가능한 쿼리를 N+1번의 쿼리 발생으로 인한 성능 저하 문제를 말함.
(배치를 쓰면 2번 정도에 끝남..?)
데이터베이스에 1개의 Category와 이 카테고리에 속하는 N개의 Question이 있다고 가정
1. 첫 번째 쿼리는 Category를 가져오는 쿼리
1. 각 Category에 속하는 Question을 가져오기 위한 추가 쿼리들: 만약 데이터베이스에 10개의 Category가 있다면, 각 Category에 대해 연결된 Question을 가져오기 위해 추가적인 쿼리가 필요합니다. 각 Category당 하나의 쿼리가 실행되므로, 이는 10번의 추가 쿼리를 발생
객체를 직접 넣지 않고 fk만 넣을 수도 있음.
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number
@Column()
name: string
@Column({ nullable: true })
profileId: number
@OneToOne((type) => Profile)
@JoinColumn() // 외래 키 컬럼 이름을 자동으로 'profileId'로 지정
profile: Profile
}

관계를 설정하면 해당 필드에 외래키가 들어가는 줄 알았음.
근데 그게 아니라 이건 일종의 참조 필드로 실제 데이터베이스 테이블에 명시되는 건 아니였음.
만약 외래키를 표시하고 싶다면 별도의 Id 필드를 만들어 줘야 함.

@OneToOne 같은 관계 설정 데코레이터와 @JoinColumn을 사용하는 것은 객체 간의 관계를 정의하는 방법이다!

