멘토님의 한 마디
TypeORM 공식 문서 안 보셨죠?
질문의 수준에서 티가 난 듯 😂
공식문서 읽기 시작!
https://typeorm.io/
이건 테이블은 생성되지만, 컬럼은 생성 안 됨.
import { Entity } from "typeorm"
@Entity()
export class Photo {
id: number
name: string
description: string
filename: string
views: number
isPublished: boolean
}
이렇게 해야지 필드와 컬럼이 매칭된다.
import { Entity, Column } from "typeorm"
@Entity()
export class Photo {
@Column()
id: number
@Column()
name: string
@Column()
description: string
@Column()
filename: string
@Column()
views: number
@Column()
isPublished: boolean
}
기본키는 반드시 있어야 한다고 한다. 예전에 멘토님께 질문했던 내용에 대한 답을 공식문서에서 찾았음.
import { Entity, Column, PrimaryColumn } from "typeorm"
@Entity()
export class Photo {
@PrimaryColumn()
id: number
@Column()
name: string
@Column()
description: string
@Column()
filename: string
@Column()
views: number
@Column()
isPublished: boolean
}
TypeORM을 사용하면, 개발자는 객체 지향적인 방식으로 데이터베이스 스키마를 정의하고 관리할 수 있으며, SQL 쿼리를 직접 작성하는 번거로움 없이 데이터베이스 작업을 수행할 수 있음.
EntityManager는 앱의 모든 엔터티를 조작할 수 있다!
import { Photo } from "./entity/Photo"
import { AppDataSource } from "./index"
const savedPhotos = await AppDataSource.manager.find(Photo)
console.log("All photos from the db: ", savedPhotos)
리포지토리는 특정 엔터티의 자체 저장소
엔터티 매니저로 앱을 명시해서 사용할 수 있지만, 하나의 테이블은 자신만으 레포지토리를 가질 수 있고 이걸 사용하면 해당 엔터티 조작이 더 편리해진다.
import { Photo } from "./entity/Photo"
import { AppDataSource } from "./index"
const photo = new Photo()
photo.name = "Me and Bears"
photo.description = "I am near polar bears"
photo.filename = "photo-with-bears.jpg"
photo.views = 1
photo.isPublished = true
const photoRepository = AppDataSource.getRepository(Photo)
await photoRepository.save(photo)
console.log("Photo has been saved")
const savedPhotos = await photoRepository.find()
console.log("All photos from the db: ", savedPhotos)
@OneToOne(() => Photo)
@JoinColumn()
photo: Photo
외래키를 가지고 있는 쪽이 owner side
이런 식으로 양 쪽에 다 명시해 주면 양방향으로 관계를 맺을 수 있다.
Owner side에서는 외래키를 가지고 있음. 다른 한 쪽은 관계를 참조만 함.
반대는 Inverse side
@Entity()
// 첫 번째 파라미터 () => Photo는 관계의 대상 엔터티 타입을 지정
// 두 번째 파라미터 (photo) => photo.metadata는 관계의 반대쪽에서 현재 엔터티를 참조하는 프로퍼티를 지정
export class PhotoMetadata {
/* ... other columns */
@OneToOne(() => Photo, (photo) => photo.metadata)
@JoinColumn()
photo: Photo
}
@Entity()
export class Photo {
/* ... other columns */
@OneToOne(() => PhotoMetadata, (photoMetadata) => photoMetadata.photo)
metadata: PhotoMetadata
}
이렇게 바꿔야 함
//
@OneToOne(() => Photo, (photo) => photo.metadata)
@JoinColumn()
photo: Relation<Photo> // 여기
//
@OneToOne(() => PhotoMetadata, (photoMetadata) => photoMetadata.photo)
metadata: Relation<PhotoMetadata> // 여기
const photoRepository = AppDataSource.getRepository(Photo)
const photos = await photoRepository.find({
relations: {
metadata: true,
},
})
간단한게는 find 메서드로 해도 됨. 근데 복잡한 쿼리는 쿼리 빌더 쓰면 됨.
TypeORM에서 relations: { metadata: true } 옵션
특정 엔터티를 조회할 때 해당 엔터티와 관계가 설정된 다른 엔터티들도 함께 로드하도록 지정하는 설정
const photos = await AppDataSource.getRepository(Photo)
.createQueryBuilder("photo")
.innerJoinAndSelect("photo.metadata", "metadata")
.getMany()
하나의 객체가 저장될 때 연관된 다른 객체도 자동적으로 저장되게 할 수 있음!
@OneToOne(() => PhotoMetadata, (metadata) => metadata.photo, {
cascade: true,
})
metadata: PhotoMetadata
이렇게 하면 사진과 메타데이터 관계에서 메타테이터는 리포지토리로 save를 호출하지 않고,
photo.metadata에 넣어놓고, Photo만 저장하면 casecade된 메타데이터도 저장됨.
만약 이렇게 안하면 메타데이터를 저장해서 FK를 받고, 그걸 Photo에 넣어주는 번거로움이 있음.
한 명의 작가는 다수의 사진을 가질 수 있다.
//
@Entity()
export class Author {
@PrimaryGeneratedColumn()
id: number
@Column()
name: string
@OneToMany(() => Photo, (photo) => photo.author) // note: we will create author property in the Photo class below
photos: Photo[]
}
// 항상 ManyToOne이 Owner Side가 됨
@Entity()
export class Photo {
/* ... other columns */
@ManyToOne(() => Author, (author) => author.photos)
author: Author
}
OneToMany 관계가 설정된 엔터티는 반드시 ManyToOne 관계가 설정된 엔터티와 연결되어야 합니다. 이는 OneToMany가 단순히 관계를 맺고 있는 엔터티를 참조하는 목록을 유지하기 때문입니다. 실제 외래 키 관리와 관계의 정의는 ManyToOne 쪽에서 이루어집니다.

@Entity()
export class Album {
@PrimaryGeneratedColumn()
id: number
@Column()
name: string
@ManyToMany(() => Photo, (photo) => photo.albums)
@JoinTable()
photos: Photo[]
}
// inverse side
export class Photo {
// ... other columns
@ManyToMany(() => Album, (album) => album.photos)
albums: Album[]
}
헐. M2M의 접합 테이블을 보고 그 동안 의문이 완전 해소됨.
정규화를 해야 하는 게 어떻게 필드를 배열로 관리하지? 했는데 접합 테이블에서 로우를 하나씩 쌓아가는 거였음.

복잡한 쿼리를 SQL로 짜지 않고 빌더로 프로그래밍적으로 생성할 수 있음.
const photos = await AppDataSource.getRepository(Photo)
.createQueryBuilder("photo") // first argument is an alias. Alias is what you are selecting - photos. You must specify it.
.innerJoinAndSelect("photo.metadata", "metadata")
.leftJoinAndSelect("photo.albums", "album")
.where("photo.isPublished = true")
.andWhere("(photo.name = :photoName OR photo.name = :bearName)")
.orderBy("photo.id", "DESC")
.skip(5)
.take(10)
.setParameters({ photoName: "My", bearName: "Mishka" })
.getMany()