TypeORM에 관해 공부하다가 공식홈페이지를 방문하게 되었다.

다른 블로그 글보다 훨씬 깔끔하게 적혀있으며 이해하기 쉬웠다.

가능하면 공식홈페이지에서 보는 것을 추천한다.

다만 나는 내가 필요한 부분만 골라서 요약정리할 생각이다.

 

공식홈페이지 https://typeorm.io/

 

TypeORM - Amazing ORM for TypeScript and JavaScript (ES7, ES6, ES5). Supports MySQL, PostgreSQL, MariaDB, SQLite, MS SQL Server,

 

typeorm.io

 

단계별 가이드

 

# 모델 생성

데이터베이스 작업은 테이블 생성에서 시작됩니다. TypeORM에게 데이터베이스 테이블을 생성하도록

어떻게 지시합니까? 답은 모델을 통해서입니다. 앱의 모델은 데이터베이스 테이블입니다.

export class Photo {
    id: number
    name: string
    description: string
    filename: string
    views: number
    isPublished: boolean
}

그리고 데이터베이스에 사진을 저장하려고 합니다. 데이터베이스에 물건을 저장하려면

먼저 데이터베이스 테이블이 필요하고 모델에서 데이터베이스 테이블이 생성됩니다. 

모든 모델이 아니라 엔티티 로 정의한 모델만 해당됩니다 .

 

# 엔티티 생성

엔티티데코레이터 가 장식한 모델입니다. 이러한 모델에 대한 데이터베이스 테이블이 생성됩니다. 

TypeORM의 모든 곳에서 엔티티로 작업합니다. 로드/삽입/업데이트/제거하고 다른 작업을 수행할 수 있습니다.

import { Entity } from "typeorm"

@Entity()
export class Photo {
    id: number
    name: string
    description: string
    filename: string
    views: number
    isPublished: boolean
}

우리는 데이터베이스 테이블을 생성했지만 열 없이 존재할 수 있는 테이블은 무엇입니까? 

데이터베이스 테이블에 몇 개의 열을 만들어 보겠습니다.

 

# 테이블 열 추가 (최종)

데이터베이스 열을 추가하려면 데코레이터를 사용하여 열로 만들려는 엔터티의 속성을 장식하기만 하면 됩니다

import { Entity, Column, PrimaryGeneratedColumn } from "typeorm"

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

    @Column({
        length: 100,
    })
    name: string

    @Column("text")
    description: string

    @Column()
    filename: string

    @Column("double")
    views: number

    @Column()
    isPublished: boolean
}

 

# 기본메소드

find(): 주어진 조건에 맞는 데이터를 조회하는 메소드입니다. 조회 결과는 배열 형태로 반환됩니다.

findOneBy(): 주어진 조건에 맞는 데이터 중 첫 번째 데이터를 조회하는 메소드입니다. 
             조회 결과는 객체 형태로 반환됩니다.

findAndCount(): 주어진 조건에 맞는 데이터를 조회하고, 
해당 데이터의 총 개수를 반환하는 메소드입니다. 조회 결과는 배열 형태로 반환됩니다.

save(): 새로운 데이터를 추가하거나, 기존 데이터를 수정하는 메소드입니다. 
만약 주어진 데이터가 이미 존재한다면 수정되고, 존재하지 않는다면 새로 추가됩니다.

remove(): 주어진 조건에 맞는 데이터를 삭제하는 메소드입니다.

updateOne(): 주어진 조건에 맞는 첫 번째 문서를 수정하는 메소드입니다.

updateMany(): 주어진 조건에 맞는 모든 문서를 수정하는 메소드입니다.

countDocuments(): 주어진 조건에 맞는 문서의 개수를 반환하는 메소드입니다.

distinct(): 주어진 필드에 대해 중복을 제거한 유일한 값을 반환하는 메소드입니다.

aggregate(): 복잡한 데이터 처리를 위한 파이프라인을 생성하여 데이터를 조회하는 메소드입니다.

populate(): 다른 컬렉션에 있는 데이터를 참조하고 있는 필드의 값을 해당 컬렉션의 데이터로 채우는 메소드입니다.

sort(): 조회 결과를 정렬하는 메소드입니다.

limit(): 조회 결과를 제한하는 메소드입니다.

skip(): 조회 결과에서 일부 데이터를 제외하는 메소드입니다.

 

# 일대일 관계 만들기

우리는 또한 @JoinColumn데코레이터를 추가하여 관계의 이 쪽이 관계를 소유할 것임을 나타냅니다. 

관계는 단방향 또는 양방향일 수 있습니다. 관계형의 한쪽만 소유할 수 있습니다. 

데코레이터를 사용하는 @JoinColumn은 관계의 소유자 측에서 필요합니다.

@Entity()
export class PhotoMetadata {
    /* ... other columns */
    @OneToOne(() => Photo)
    @JoinColumn()
    photo: Photo
}

# 관계의 반대면

관계는 단방향 또는 양방향일 수 있습니다. 현재 PhotoMetadata와 Photo 간의 관계는 단방향입니다. 

관계의 소유자는 PhotoMetadata이고 Photo는 PhotoMetadata에 대해 아무것도 모릅니다. 

이로 인해 사진 쪽에서 PhotoMetadata에 액세스하기가 복잡해집니다.

이 문제를 해결하려면 역 관계를 추가하고 PhotoMetadata와 Photo 간의 관계를 양방향으로 만들어야 합니다. 

@Entity()
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
}

 

# 다른 표기법(GPT 문의결과)

    @OneToOne(() => PhotoMetadata, (photoMetadata) => photoMetadata.photo)
    metadata: PhotoMetadata

    @OneToOne(() => PhotoMetadata, (photoMetadata) => photoMetadata.photo)
    metadata: Relation<PhotoMetadata>

결론은 두번째 코드를 사용하는 것이 좋아보인다.

더보기

첫 번째 코드에서는 metadata 속성이 PhotoMetadata 객체를 직접 참조하고 있습니다.

반면 두 번째 코드에서는 metadata 속성의 타입이 Relation<PhotoMetadata>으로 정의되어 있습니다.

 

이 차이점은 타입스크립트 타입과 ORM의 동작 방식과 관련이 있습니다.

첫 번째 코드에서는 metadata 속성이 PhotoMetadata 객체를 직접 참조하므로,

해당 속성을 사용하여 Photo 객체를 로드하면 PhotoMetadata 객체가 자동으로 연결됩니다.

 

반면 두 번째 코드에서는 metadata 속성의 타입이 Relation<PhotoMetadata>으로 정의되어 있으므로,

해당 속성을 사용하여 Photo 객체를 로드하더라도 PhotoMetadata 객체는 자동으로 연결되지 않습니다.

대신, 해당 속성을 사용하여 PhotoMetadata 객체를 직접 로드해야 합니다.

 

따라서 두 번째 코드에서는 metadata 속성을 사용하여 PhotoMetadata 객체를 로드하기 전에 Relation<PhotoMetadata> 타입으로 선언된 속성을 사용하여 필요한 쿼리를 생성하고,

이를 실행하여 PhotoMetadata 객체를 로드해야 합니다. 이 방식은 ORM의 성능을 최적화하고,

불필요한 데이터를 로드하지 않도록 도와줍니다.

 

# 관계와 함께 객체 로드하기

객체로드 하는 방법을 두가지가 있다. 차이점은 간단하게 할거냐, 상세하게 할거냐이므로 선택인듯 하다.

 

첫번째, TypeORM Repository Query 

const photos = await photoRepository.find({
    relations: {
        metadata: true,
    },
})

두번째, TypeORM Query builder

const photos = await AppDataSource.getRepository(Photo)
    .createQueryBuilder("photo")
    .innerJoinAndSelect("photo.metadata", "metadata")
    .getMany()

 

# 캐스케이드를 사용하여 관련 객체를 자동으로 저장

다른 개체가 저장될 때마다 관련 개체를 저장하려는 경우 관계에서 캐스케이드 옵션을 설정할 수 있습니다.

export class Photo {
    // ... other columns

    @OneToOne(() => PhotoMetadata, (metadata) => metadata.photo, {
        cascade: true,
    })
    metadata: PhotoMetadata
}

cascade옵션에 대한 설명

더보기

cascade 옵션은 관계형 데이터베이스에서 엔티티를 저장, 업데이트, 삭제할 때

연관된 엔티티의 동작을 제어하는 옵션입니다.

엔티티가 저장, 업데이트, 삭제될 때 관련된 PhotoMetadata 엔티티도 함께 저장, 업데이트, 삭제됩니다.

cascade 옵션은 일대일, 일대다, 다대일, 다대다 관계 모두에서 사용할 수 있습니다.

cascade 옵션은 아래와 같은 값들을 가질 수 있습니다.

  • remove: 해당 엔티티가 삭제될 때 연관된 엔티티도 함께 삭제됩니다.
  • save: 해당 엔티티가 저장될 때 연관된 엔티티도 함께 저장됩니다.
  • update: 해당 엔티티가 업데이트될 때 연관된 엔티티도 함께 업데이트됩니다.
  • soft-remove: 해당 엔티티가 삭제될 때 연관된 엔티티도 삭제 대신 삭제 플래그를 설정합니다.
  • recover: 해당 엔티티가 복원될 때 연관된 엔티티도 함께 복원됩니다.

cascade 옵션은 데이터베이스 작업을 간편하게 해주지만, 관련된 모든 엔티티가 동일한 작업을 수행하기 때문에 주의해서 사용해야 합니다. 특히 대규모 애플리케이션에서는 적절한 cascade 옵션을 설정하는 것이 성능 및 데이터 무결성 관리에 중요합니다.

 

# 다대일/일대다 관계생성

@Entity()
export class Author {
    @OneToMany(() => Photo, (photo) => photo.author) 
    //참고: 아래 Photo 클래스에 작성자 속성을 생성합니다.
    photos: Photo[]
}
@Entity()
export class Photo {
    @ManyToOne(() => Author, (author) => author.photos)
    author: Author
}

 

# 다대다 관계생성

@Entity()
export class Album {
    @ManyToMany(() => Photo, (photo) => photo.albums)
    @JoinTable()
    photos: Photo[]
}
export class Photo {
    @ManyToMany(() => Album, (album) => album.photos)
    albums: Album[]
}

애플리케이션을 실행한 후 ORM은 album_photos_photo_albums 접합 테이블을 생성합니다 .

 

AlbumORM에서 연결에 클래스를 등록하는 것을 잊지 마십시오 .

 

이제 데이터베이스에 앨범과 사진을 삽입해 보겠습니다.

import { AppDataSource } from "./index"

// create a few albums
const album1 = new Album()
album1.name = "Bears"
await AppDataSource.manager.save(album1)

const album2 = new Album()
album2.name = "Me"
await AppDataSource.manager.save(album2)

// create a few photos
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
photo.albums = [album1, album2]
await AppDataSource.manager.save(photo)

// now our photo is saved and albums are attached to it
// now lets load them:
const loadedPhoto = await AppDataSource.getRepository(Photo).findOne({
    where: {
        id: 1,
    },
    relations: {
        albums: true,
    },
})

 

 

 

 

 

 

+ Recent posts