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

[TypeORM] Relations

by YuminK 2023. 9. 4.

https://orkhan.gitbook.io/typeorm/docs/relations

 

What are relations

1. @OneToOne

2. @ManyToOne

3. @OneToMany

4. @ManyToMany

 

Relation options

관계 설정을 위해 처리하는 옵션

 

eager: booleam - true로 처리되면 메인 엔티티를 로드할 때 연관된 엔티티가 같이 로드 된다. 

QueryBuilder나 find* 메소드 사용시.

 

casecade: boolean | ("insert | "update")[] - true로 처리되면 연관된 엔티티가 DB에 추가되고 업데이트 된다. 

배열을 통해 옵션을 넘기는 것도 가능하다. 

 

onDelete: "RESTRICT" | "CASCADE" | "SET NULL" - 외래키 행동에 대한 조건을 설정한다. 연관된 오브젝트 삭제시

 

nullable: boolean - 관계의 column이 nullable인지 아닌지 결정한다. 기본은 nullable

 

oprhanedRowAction: "nullify" | "delete" | "soft-delete" | disable

- 부모가 DB 상에 존재하는 자식 객체 없이 저장되었을 때(cascade = true) 어떤 식으로 처리할지 정한다. 

delete는 고아객체를 DB상에서 없앤다. soft-delete는 삭제 되었다는 마크 처리만 진행한다.

nullify는 relation key를 삭제한다. disable은 그대로 둔다. 

 

Cascades

예시)

import { Entity, PrimaryGeneratedColumn, Column, ManyToMany } from "typeorm"
import { Question } from "./Question"

@Entity()
export class Category {
    @PrimaryGeneratedColumn()
    id: number

    @Column()
    name: string

    @ManyToMany((type) => Question, (question) => question.categories)
    questions: Question[]
}

 

import {
    Entity,
    PrimaryGeneratedColumn,
    Column,
    ManyToMany,
    JoinTable,
} from "typeorm"
import { Category } from "./Category"

@Entity()
export class Question {
    @PrimaryGeneratedColumn()
    id: number

    @Column()
    title: string

    @Column()
    text: string

    @ManyToMany((type) => Category, (category) => category.questions, {
        cascade: true,
    })
    @JoinTable()
    categories: Category[]
}

 

const category1 = new Category()
category1.name = "ORMs"

const category2 = new Category()
category2.name = "Programming"

const question = new Question()
question.title = "How to ask questions?"
question.text = "Where can I ask TypeORM-related questions?"
question.categories = [category1, category2]
await dataSource.manager.save(question)

 

category1과 category2의 save를 호출하지 않았다. 자동으로 추가가 된다. 

 

Casecade는 좋고 쉬운 방법으로 보이지만, 원하지 않는 오브젝트가 DB에 저장될 때 버그와 보안 이슈를 가져올 수 있다. 

또한 새로운 객체를 DB에 저장할 때 모호한 방식을 제공한다. 

 

Cascade Options

cascade 옵션은 boolean으로 처리되거나 Array로 처리할 수 있다. 기본은 false이고 true로 설정시 모든 캐스캐이드 옵션을 반영한다.

 

예시)

@Entity(Post)
export class Post {
    @PrimaryGeneratedColumn()
    id: number

    @Column()
    title: string

    @Column()
    text: string

    // Full cascades on categories.
    @ManyToMany((type) => PostCategory, {
        cascade: true,
    })
    @JoinTable()
    categories: PostCategory[]

    // Cascade insert here means if there is a new PostDetails instance set
    // on this relation, it will be inserted automatically to the db when you save this Post entity
    @ManyToMany((type) => PostDetails, (details) => details.posts, {
        cascade: ["insert"],
    })
    @JoinTable()
    details: PostDetails[]

    // Cascade update here means if there are changes to an existing PostImage, it
    // will be updated automatically to the db when you save this Post entity
    @ManyToMany((type) => PostImage, (image) => image.posts, {
        cascade: ["update"],
    })
    @JoinTable()
    images: PostImage[]

    // Cascade insert & update here means if there are new PostInformation instances
    // or an update to an existing one, they will be automatically inserted or updated
    // when you save this Post entity
    @ManyToMany((type) => PostInformation, (information) => information.posts, {
        cascade: ["insert", "update"],
    })
    @JoinTable()
    informations: PostInformation[]
}

 

@JoinColumn

외래키를 가진 테이블을 정의할 뿐만 아니라, join column 이름과 referenced column 이름을 설정할 수 있도록 한다.

@JoinColumn을 설정할 때, 자동으로 propertyName과 referencedColumnName으로 처리된 컬럼을 생성한다. 

 

@ManyToOne(type => Category)
@JoinColumn() // this decorator is optional for @ManyToOne, but required for @OneToOne
category: Category;

이는 categoryId Column을 데이터베이스 상에 추가한다. 바꾸고 싶은 경우에는 다음과 같이 처리할 수 있다. 

 

@ManyToOne(type => Category)
@JoinColumn({ name: "cat_id" })
category: Category;

JoinColumns는 항상 다른 칼럼을 참조한다.

기본적으로 주요키를 참조하는데 다른 칼럼을 참조하길 원하는 경우 다음과 같이 처리할 수 있다. 

 

@ManyToOne(type => Category)
@JoinColumn({ referencedColumnName: "name" })
category: Category;

 

이 때, Category 엔티티의 Id를 참조하는 것이 아닌 name 값을 참조하게 된다.

컬럼 이름은 categoryName으로 처리된다. 

 

// 근데 어지간하면 그냥 id로 참조할 것 같다. (다른 테이블이랑 다르게 name으로 참조..? 굳이)

 

또한 여러 컬럼을 지정할 수 있다. 다시 한번 기본적으로 엔티티의 주요 키를 참조하지 않는다. 

레퍼런스 네임 값을 지정해야 한다. 

 

@ManyToOne(type => Category)
@JoinColumn([
    { name: "category_id", referencedColumnName: "id" },
    { name: "locale_id", referencedColumnName: "locale_id" }
])
category: Category;

 

// 실무에서 이렇게 복잡하게 쓸까... 싶은 생각이 든다. 

 

@JoinTable options

ManyToMany 관계에서 사용된다. ManyToMany. 관계에서는 TypeORM에 의해 자동으로 중간 테이블이 생성된다. 참조하는 엔티티와 연관된 칼럼을 포함한다. 중간 테이블에 생성되는 컬럼 이름과 연관 컬럼을 수정할 수 있다. 

중간 테이블의 이름을 바꾸는 동작도 가능하다. 

 

@ManyToMany(type => Category)
@JoinTable({
    name: "question_categories", // table name for the junction table of this relation
    joinColumn: {
        name: "question",
        referencedColumnName: "id"
    },
    inverseJoinColumn: {
        name: "category",
        referencedColumnName: "id"
    }
})
categories: Category[];

 

destination 테이블에서 복합된 주요 키를 가진 경우, array로 지정해야 한다.  

댓글