728x90
반응형

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,
    },
})

 

 

 

 

 

 

728x90
반응형

+ Recent posts