TypeORM

관계(Relation)

남느 2025. 5. 7. 16:37

관계(Relation)

관계형 데이터베이스는 데이터 중복을 방지하기 위해 테이블간 관계를 사용합니다.

테이블 간 관계에는 네 종류가 있습니다.

  • 일대일 관계(1:1, One to One)
  • 일대다 관계(1:N, One to Many)
  • 다대일 관계(N:1, Many to One)
  • 다대다 관계(N:M, Many to Many)

PL/SQL이나 MyBatis를 사용하던 사람들에게 주의사항

데이터베이스 테이블은 부모 자식 관계가 없습니다.
하지만 ORM에는 부모 자식 관계가 있다고 생각하세요.

관계 옵션

옵션 설명
eager true 설정 시 find(), findOne(), QueryBuilder 사용 시 참조하는 엔티티를 항상 조회합니다.
기본값 fasle.
cascade true 설정 시 insert, update 쿼리 시 참조하는 엔티티의 행이 함께 insert 혹은 update 됩니다.
기본값 false.

true, false 대신 배열에 쿼리 종류를 지정할 수도 있습니다.
[ 'insert', 'update', 'remove', 'soft-remove', 'recover' ]
onDelete 부모 엔티티의 행이 삭제될 때 외래키가 어떻게 동작해야하는지 지정합니다.
onDelete 속성은 자식 엔티티에서 사용해야 합니다.

‘NO ACTION’: 아무것도 안함. 기본값
‘CASCADE’: 참조하는 row도 같이 삭제
‘SET NULL’: null로 변경
‘DEFAULT’: 테이블의 기본 설정 값으로 변경
‘RESTRICT’: 참조하는 row가 있는 경우 삭제 불가
nullable 관계키가 null 허용인지 여부를 지정합니다.

true: 기본값
false: 관계 컬럼에 값이 없으면 저장 불가능
orphanedRowAction 부모가 삭제되는 경우 자식의 동작을 정의합니다. (고아 객체라고도 하네요.)

기본값 disable.
delete: 자식 삭제(update의 경우인 듯? insert는 불가능하니까)
soft-delete: 자식 soft-delete
nullify: 관계키만 삭제
disable: 관계는 유지. 삭제하려면 자식의 저장소(repository)를 사용해야 합니다.

일대일 관계

일대일 관계는 두 가지 종류가 있습니다.

  • 단방향 일대일 관계
  • 양방향 일대일 관계

자식 Profile를 부모 User 엔티티가 참조하는 예시를 단방향, 양방향으로 각각 만들어보겠습니다.

단방향 일대일 관계

두 엔티티 중 하나의 엔티티에만 관계 데코레이터를 선언하면 단방향 일대일 관계가 됩니다.

부모 엔티티에 @OneToOne(), @JoinColumn() 데코레이터를 사용합니다.

 

이때는 자식 엔티티의 PK를 부모 엔티티의 외래키로 저장합니다.

 

profile.entity.ts

import { Entity, PrimaryGeneratedColumn, Coloumn } from 'typeorm'

@Entity()
export class Profile {
	@PrimaryGeneratedColumn()
	id: number;

	@Column()
	name: string;
}

user.entity.ts

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

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

    @Column()
    username: string

    @OneToOne(() => Profile,{
      casecade: true,
      eager: true,
    })
    profile: Profile
}

단방향 관계에서는 casecade 옵션에 'remove'를 넣거나, onDelete 옵션에 'CASCADE'를 넣어도 자식 엔티티의 데이터가 삭제되지 않습니다.

양방향 일대일 관계

두 엔티티 모두에 @OneToOne() 데코레이터를 사용해야 합니다.

자식 엔티티에 @JoinColumn() 데코레이터를 사용합니다.

 

이때는 부모 엔티티의 PK를 자식 엔티티의 외래키로 저장합니다.

 

onDelete 옵션을 자식 엔티티에서 사용하면 부모 엔티티의 행 삭제 시 자식 엔티티의 행도 삭제됩니다.


profile.entity.ts

import {
  Entity,
  PrimaryGeneratedColumn,
  Column,
  OneToOne,
  JoinColumn,
} from 'typeorm';
import { User } from './user.entity';

@Entity()
export class Profile {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  name: string;

  @OneToOne(() => User, (user) => user.id, {
    onDelete: 'CASCADE',
  })
  @JoinColumn()
  user: string;
}

user.entity.ts

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

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

    @Column()
    username: string

    @OneToOne(() => Profile, (profile) => profile.user, {
      // casecade와 eager 옵션은 권장되지 않지만 예시로 작성했습니다.
      casecade: true, 
      eager: true,
    })
    profile: Profile
}

일대다, 다대일 관계

부모 엔티티에 @OneToMany() 데코레이터를, 자식 엔티티에 @ManyToOne() 데코레이터를 사용합니다.
@JoinColumn() 데코레이터는 사용하지 않습니다.

부모 엔티티의 PK를 자식 엔티티의 외래키로 저장합니다.

 

photo.entity.ts

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
}

 

user.entity.ts

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[]
}

 

 

관계를 사용한 조회

조회 시 relation 사용

w

eager 옵션 사용

 

 

관계를 사용한 저장(insert, update)

저장 시 컬럼 값 지정

 

cascade 옵션