이번 로그는 TypeORM을 이용하여 데이터베이스 테이블 간의 관계설정과 Join 하는 방법에 대해 기록하고자 한다.
TypeORM에서는 Sequelize와 같이 Entity를 정의하면서 다른 Entitiy와 관계를 설정할 수 있는 방법을 지원하고 있다.Sequelize와 다소 차이가 있어서 처음에는 헷갈렸지만 익숙해지니 Sequelize 보다 정의하는 방식이 직관적으로 느껴졌다.
그리고 타입스크립트를 사용하고 있으므로 관계설정에 의해 생성되는 외래키 컬럼도 타입을 정의해야 했다. 아직 타입스크립트가 익숙하지 않아 처음에는 타입에러가 엄청났지만 하나하나 디버깅하면서 타입스크립트와 타입의 중요성에 대해 알아갈 수 있었다.
그러면 아래의 House와 Image간의 One to Many 관계의 예시를 보자.
하나의 House가 여러 Image를 갖는 House(1) : Image(N)의 관계일 경우, 먼저 House에서 @OneToMany 데코레이션을 이용하여 관계를 설정한다.
import {
BaseEntity,
Entity,
PrimaryGeneratedColumn,
Column,
OneToOne,
JoinColumn,
CreateDateColumn,
UpdateDateColumn,
OneToMany
} from 'typeorm';
import { Image } from './Image';
@Entity()
export class House extends BaseEntity {
@PrimaryGeneratedColumn()
id!: number;
@Column()
plan!: string;
@Column()
type!: string;
@Column()
year!: string;
@Column()
access!: string;
@Column({ type: 'boolean' })
status!: boolean;
@Column({ type: 'boolean' })
display!: boolean;
@Column({ nullable: true })
startTime!: string;
@Column({ nullable: true })
endTime!: string;
@Column('simple-array')
location!: number[];
@Column()
adminDistrict!: string;
@Column()
title!: string;
@Column({ type: 'text' })
description!: string;
@Column({ type: 'text' })
houseRule!: string;
@Column({ type: 'boolean' })
isActive!: boolean;
@CreateDateColumn({ name: 'created_at' })
createdAt!: Date;
@UpdateDateColumn({ name: 'updated_at' })
updatedAt!: Date;
// House(1) <-> Image(*)
@OneToMany(
(type) => Image,
(image) => image.house,
)
images!: Image[];
}
그리고 반대 쪽인 Image에서는 @ManyToOne 데코레이션을 이용하여 House와 반대의 관계를 설정한다.
import {
BaseEntity,
Entity,
PrimaryGeneratedColumn,
Column,
CreateDateColumn,
UpdateDateColumn,
ManyToOne,
} from 'typeorm';
import { House } from './House';
@Entity()
export class Image extends BaseEntity {
@PrimaryGeneratedColumn()
id!: number;
@Column()
filePath!: string;
@Column()
fileName!: string;
@CreateDateColumn({ name: 'created_at' })
createdAt!: Date;
@UpdateDateColumn({ name: 'updated_at' })
updatedAt!: Date;
@Column({ type: 'boolean' })
isActive!: boolean;
// House(1) <-> Image(*)
@ManyToOne(
(type) => House,
(house) => house.images
)
house!: House;
}
이렇게 관계설정이 완료된 후, mysql에서 테이블이 어떻게 만들어졌는지 확인해보면 아래와 같다.
Image 테이블에 houseId가 MUL 외래키로 만들어진 것을 확인할 수 있다 !
TypeORM을 이용하면 이와 같이 간단히 관계를 설정할 수 있다. 😎
관계가 설정된 테이블의 데이터를 삭제할 때 아래와 같이 외래키로 인해 삭제가 되지 않는 에러가 발생하게 된다.
(node:14076) UnhandledPromiseRejectionWarning: QueryFailedError: ER_ROW_IS_REFERENCED_2: Cannot delete or update a parent row: a foreign key constraint fails (`billyzip`.`review`, CONSTRAINT `FK_549fda2e31af06d24d2ae10cbd3` FOREIGN KEY (`houseId`) REFERENCES `house` (`id`))
at new QueryFailedError
이를 해결하기 위해서는 관계설정을 할 때 'onDelete'라고 하는 옵션을 설정해줘야 한다. 설정하는 방법은 1:N의 관계의 경우 Many To One가 설정되어 있는 모델에서 아래와 같이 추가해주면 된다.
import {
BaseEntity,
Entity,
PrimaryGeneratedColumn,
Column,
CreateDateColumn,
UpdateDateColumn,
ManyToOne,
} from 'typeorm';
import { House } from './House';
@Entity()
export class Image extends BaseEntity {
@PrimaryGeneratedColumn()
id!: number;
@Column()
filePath!: string;
@Column()
fileName!: string;
@CreateDateColumn({ name: 'created_at' })
createdAt!: Date;
@UpdateDateColumn({ name: 'updated_at' })
updatedAt!: Date;
@Column({ type: 'boolean' })
isActive!: boolean;
// House(1) <-> Image(*)
@ManyToOne(
(type) => House,
(house) => house.images, { nullable: false, onDelete: 'CASCADE' }
)
house!: House;
}
감사합니다. 덕분에 nest.js + typeorm에서 발생한 문제를 고칠 수 있었습니다. :)