(TypeORM) TypeORM - Relations

최건·2025년 5월 10일

참고 문서

Relation 종류

  1. 일대일 (One-to-One)
  • 한 엔티티가 다른 엔티티와 1:1로 매핑됩니다. 예를 들어, 한 사용자가 하나의 프로필을 가질 수 있다.
    @OneToOne(() => Profile)
    @JoinColumn()
    profile: Profile;
    
  1. 일대다 / 다대일 (One-to-Many / Many-to-One)
  • 한 엔티티가 여러 엔티티와 연관되거나, 여러 엔티티가 하나의 엔티티와 연관됩니다. 예를 들어, 한 사용자가 여러 게시물을 작성할 수 있다.

    @OneToMany(() => Post, post => post.user)
    posts: Post[];
    
    @ManyToOne(() => User, user => user.posts)
    user: User;
    
  1. 다대다 (Many-to-Many)
  • 여러 엔티티가 서로 다수와 연관됩니다. 예를 들어, 여러 학생이 여러 과목을 수강할 수 있다.
    @ManyToMany(() => Subject)
    @JoinTable()
    subjects: Subject[];
    

Relation Option

  • eager: true로 설정하면, 관계된 엔티티를 자동으로 로드한다.
  • cascade: 연관된 엔티티를 자동으로 삽입, 업데이트, 삭제할 수 있다. 예: cascade: true 또는 cascade: ['insert', 'update']
  • onDelete: 부모 엔티티 삭제 시 자식 엔티티의 동작을 정의한다.
    옵션: 'RESTRICT', 'CASCADE', 'SET NULL'
  • nullable: 관계 컬럼이 NULL을 허용할지 여부를 설정한다.
  • orphanedRowAction: 부모 엔티티에서 제거된 자식 엔티티의 처리 방식을 정의한다. 옵션: 'nullify', 'delete', 'soft-delete', 'disable'

cascade란?

cascade연관된 엔티티도 함께 저장(insert), 수정(update), 삭제(remove)할 수 있도록 하는 옵션

  • 예를 들어, Question 엔티티를 저장할 때, 연결된 Category 엔티티도 자동으로 저장되게 만들 수 있다.
  • 이를 위해 @ManyToMany, @OneToMany, @OneToOne 등의 관계 설정에 cascade 옵션을 지정한다.

Category Entity

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

    @Column()
    name: string

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

Question Entity

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

    @Column()
    title: string

    @Column()
    text: string

    @ManyToMany(() => Category, category => category.questions, {
        cascade: true,  // 중요 포인트
    })
    @JoinTable()
    categories: Category[]
}

cascade: true를 설정하면 Question을 저장할 때 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() 하지 않아도, cascade 때문에 자동으로 DB에 저장됩니다.

cascade 옵션 종류

옵션명설명
"insert"새로운 객체가 있으면 자동 저장됨
"update"기존 객체가 변경되면 자동 업데이트
"remove"관계에서 빠진 객체를 DB에서도 삭제
"soft-remove"soft remove 수행
"recover"soft remove된 객체 복구

예시

@ManyToMany(() => PostCategory, {
    cascade: true // insert, update, remove 모두 포함
})
categories: PostCategory[]

@ManyToMany(() => PostDetails, {
    cascade: ["insert"] // 새로 생성된 것만 저장
})
details: PostDetails[]

@ManyToMany(() => PostImage, {
    cascade: ["update"] // 기존 객체 수정만 반영
})
images: PostImage[]

@ManyToMany(() => PostInformation, {
    cascade: ["insert", "update"]
})
informations: PostInformation[]

@JoinColumn이란?

  • @JoinColumn은 관계를 정의할 때 외래 키 컬럼을 설정하는 데 사용된다.
  • 주로 @ManyToOne 또는 @OneToOne 관계에서 사용된다.
관계 방향@JoinColumn 필요 여부
@ManyToOne선택 (자동으로 생성됨)
@OneToOne필수 (한쪽은 반드시 명시해야 함)

기본 동작

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

위 코드는 categoryId라는 컬럼(FK)을 생성하여 Category 엔티티의 id(기본 키)를 참조하는 외래 키로 사용한다.

컬럼 이름 커스터마이징

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

categoryId(자동으로 생성되는 컬럼) 대신 cat_id라는 외래 키 컬럼을 사용하도록 지정한 것.

참조 컬럼 지정 (기본키 말고 다른 컬럼)

@ManyToOne(() => Category)
@JoinColumn({ referencedColumnName: "name" })
category: Category;
  • 이 경우 외래 키는 Category.name을 참조하며, 자동으로 생성되는 컬럼(FK) 이름은 categoryName 이 된다.

다중 컬럼 조인 (복합 외래 키)

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

정확한 이해를 돕기 위해 @JoinTable의 동작 원리와 옵션들을 예제 기반으로 알기 쉽게 설명해드릴게요.


@JoinTable이란?

  • @ManyToMany 관계에서는 두 엔티티 간의 관계를 나타내기 위한 중간 테이블(junction table) 이 자동 생성된다.
  • 중간 테이블의 이름, 그리고 어떤 컬럼이 어떤 엔티티의 어떤 컬럼을 참조하는지를 설정할 수 있는 데코레이터가 @JoinTable 이다.

기본 사용 예시

@ManyToMany(() => Category)
@JoinTable()
categories: Category[];
  • 이 코드는 기본적으로 question_categories_category 같은 중간 테이블을 자동 생성하며, 컬럼은 questionId, categoryId처럼 자동으로 설정된다.

옵션 지정 예시

@ManyToMany(() => Category)
@JoinTable({
  name: "question_categories", // 중간 테이블 이름
  joinColumn: {
    name: "question", // 현재 엔티티(Question)의 FK 컬럼명
    referencedColumnName: "id" // Question의 어떤 컬럼을 참조할지 (기본은 id)
  },
  inverseJoinColumn: {
    name: "category", // 반대편 엔티티(Category)의 FK 컬럼명
    referencedColumnName: "id" // Category의 어떤 컬럼을 참조할지
  }
})
categories: Category[];
항목설명
name생성할 중간 테이블 이름 지정
joinColumn현재 엔티티(왼쪽)의 외래 키 설정
inverseJoinColumn상대 엔티티(오른쪽)의 외래 키 설정

➡ 결과적으로 생성되는 중간 테이블은 다음과 같이 생깁니다:

questioncategory
12
13

복합키(Composite Key)일 경우

예를 들어 Category의 기본키가 id + localeId 두 개라면:

@ManyToMany(() => Category)
@JoinTable({
  name: "question_categories",
  joinColumn: [
    { name: "question_id", referencedColumnName: "id" }
  ],
  inverseJoinColumn: [
    { name: "category_id", referencedColumnName: "id" },
    { name: "category_locale", referencedColumnName: "localeId" }
  ]
})
categories: Category[];
  • 이처럼 joinColumn 또는 inverseJoinColumn배열을 넣어 복합키를 설정할 수 있다.
profile
개발이 즐거운 백엔드 개발자

0개의 댓글