typeORM

손연주·2022년 1월 6일
1

typeORM 공식 문서를 읽으며 작성하였습니다.

Create a model

데이터베이스의 시작은 table을 생성하는 것이다. 모델을 이용해서 만들 수 있다. 데이터베이스에 model을 저장하기 위해서는, 데이터베이스 테이블이 필요하고 그 DB 테이블은 모델로부터 만들어져야 한다. (but only 엔티티로서 정의한 것들만)

Create an entity

Entity는 @Entity 데코레이터에 의해 decorated된 모델이다. you work with entities everywhere with TypeORM. You can load/insert/update/remove and perform other operations with them.

Let's make our Photo model as an entity:

import { Entity } from "typeorm";

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

이제 우리는 Photo entity를 위한 DB table을 만들었다. 하지만 table은 column없이 존재할 순 없으니 column을 만들어보도록 하자.

Adding table columns and a primary column

DB 컬럼을 추가하기 위해서는 @Column 데코레이터를 이용할 수 있다.
또한 각각의 entity는 적어도 하나의 primary key 컬럼을 가져야 한다. @PrimaryColumn decorator를 이용할 수 있다.

import { Entity, Column, PrimaryColumn } from "typeorm";

@Entity()
export class Photo {

    @PrimaryColumn() // PK
    id: number;

    @Column()
    name: string;

    @Column()
    description: string;

    @Column()
    filename: string;

    @Column()
    views: number;

    @Column()
    isPublished: boolean;
}

Creating an auto-generated column

auto-generated column은 @PrimaryGeneratedColumn decorator를 이용한다.

  @PrimaryGeneratedColumn()
    id: number;

Column data types

컬럼의 데이터 타입과 limited도 필요에 따라 지정할 수 있다.
괄호 안에 객체 형식으로 작성한다.

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

@Entity()
export class Photo {

    @PrimaryGeneratedColumn()
    id: number;

    @Column({ // data types limited
        length: 100
    })
    name: string;

    @Column("text")
    description: string;

    @Column()
    filename: string;

    @Column("double")
    views: number;

    @Column()
    isPublished: boolean;
}

Creating a connection to the database

entity가 만들어지면, DB와 연결을 해줘야 한다.

import "reflect-metadata";
import { createConnection } from "typeorm";
import { Photo } from "./entity/Photo";

createConnection({
    type: "mysql",
    host: "localhost",
    port: 3306,
    username: "root",
    password: "admin",
    database: "test",
    entities: [
        Photo
    ],
    synchronize: true,
    logging: false
}).then(connection => {
    // here you can start to work with your entities
}).catch(error => console.log(error));

Setting synchronize makes sure your entities will be synced with the database, every time you run the application.

app을 실행시킬 때마다 DB가 자동으로 동기화되게 해야한다.

Creating and inserting a photo into the database

Now let's create a new photo to save it in the database:

import { createConnection } from "typeorm";
import { Photo } from "./entity/Photo";

createConnection(/*...*/).then(connection => { // createConnection 메서드를 이용해 DB와 연결 

    let 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;

    return connection.manager
            .save(photo)
            .then(photo => {
                console.log("Photo has been saved. Photo id is", photo.id);
            });

}).catch(error => console.log(error));

Using Entity Manager

우리는 방금 new photo를 만들고 DB에 저장했다. 이제 우리는 EntityManager를 사용하여 저장할 수 있다.

import { createConnection } from "typeorm";
import { Photo } from "./entity/Photo";

createConnection(/*...*/).then(async connection => {

    /*...*/
    let savedPhotos = await connection.manager.find(Photo); // manager.method
    console.log("All photos from the db: ", savedPhotos);

}).catch(error => console.log(error));

Using Repositories

Now let's refactor our code and use Repository instead of EntityManager. Each entity has its own repository which handles all operations with its entity.
Repositories are more convenient to use than EntityManagers:

import { createConnection } from "typeorm";
import { Photo } from "./entity/Photo";

createConnection(/*...*/).then(async connection => {

    let 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;

    let photoRepository = connection.getRepository(Photo); // getRepository

    await photoRepository.save(photo);
    console.log("Photo has been saved");

    let savedPhotos = await photoRepository.find();
    console.log("All photos from the db: ", savedPhotos);

}).catch(error => console.log(error));

save flow

  1. createConnection 메서드를 이용해 DB와 연결
  2. Entity Manager를 이용해서 DB에 저장 또는 Repositories를 이용해 DB에 저장

CRUD

Updating in the database

Now let's load a single photo from the database, update it and save it:

import { createConnection } from "typeorm";
import { Photo } from "./entity/Photo";

createConnection(/*...*/).then(async connection => {

    /*...*/
    let photoToUpdate = await photoRepository.findOne(1);
    photoToUpdate.name = "Me, my friends and polar bears";
    await photoRepository.save(photoToUpdate);

}).catch(error => console.log(error));

그러나 .save 메서드를 이용해서 update 할 수도 있다.

  • save - Saves a given entity or array of entities. If the entity already exist in the database, it is updated. If the entity does not exist in the database, it is inserted. It saves all given entities in a single transaction (in the case of entity, manager is not transactional). Also supports partial updating since all undefined properties are skipped. Returns the saved entity/entities.

따라서 위의 코드는 이런 식으로 바뀔 수 있다.

let photoToUpdate = await photoRepository.findOne(1);
photoToUpdate.name = "Me, my friends and polar bears";
await photoRepository.save(photoToUpdate);

// .save 메서드 활용
// photoRepository가 미리 DB에 저장되었다고 가정
let photoToUpdate = await photoRepository.findOne(1); // photoRepository DB에서 findone을 한 후
await photoRepository.save([ // find된 항목의 name값을 변경해 save
    photoToUpdate.name = "Me, my friends and polar bears"
]);

Creating a one-to-one relation

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

@Entity()
export class PhotoMetadata {

    @PrimaryGeneratedColumn()
    id: number;

    @Column("int")
    height: number;

    @Column("int")
    width: number;

    @Column()
    orientation: string;

    @Column()
    compressed: boolean;

    @Column()
    comment: string;

    @OneToOne(type => Photo) // @OneToOne decorator
    @JoinColumn()
    photo: Photo;
}

@OneToOne decorator를 이용해서 두 개의 entity 사이에서 일대일 관계를 설정할 수 있다.
type => Photo는 관계를 만들고자 하는 entity의 클래스를 반환하는 함수이다. (class를 직접적으로 쓰는 것이 아니라)
() => Photo로도 작성할 수 있지만, 가독성을 위해서 위와 같이 작성하였다.

JoinColum

또한 @JoinColumn decorator도 이용할 수 있다. 관계를 소유한다는 걸 나타내는 데코레이터를 추가하는 것이다. 이 데코레이터는 관계의 소유자 측에 요구된다. 따라서 Photo가 photo_metadata의 차일드가 되는 것이다.

If you run the app, you'll see a newly generated table, and it will contain a column with a foreign key for the photo relation:

+-------------+--------------+----------------------------+
|                     photo_metadata                      |
+-------------+--------------+----------------------------+
| id          | int(11)      | PRIMARY KEY AUTO_INCREMENT |
| height      | int(11)      |                            |
| width       | int(11)      |                            |
| comment     | varchar(255) |                            |
| compressed  | boolean      |                            |
| orientation | varchar(255) |                            |
| photoId     | int(11)      | FOREIGN KEY                |
+-------------+--------------+----------------------------+

Save a one-to-one relation

Now let's save a photo, its metadata and attach them to each other.

import { createConnection } from "typeorm";
import { Photo } from "./entity/Photo";
import { PhotoMetadata } from "./entity/PhotoMetadata";

createConnection(/*...*/).then(async connection => {

    // 1. create a photo
    let 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;

    // 2. create a photo metadata
    let metadata = new PhotoMetadata();
    metadata.height = 640;
    metadata.width = 480;
    metadata.compressed = true;
    metadata.comment = "cybershoot";
    metadata.orientation = "portrait";
    metadata.photo = photo; // this way we connect them

    // 3. get entity repositories
    let photoRepository = connection.getRepository(Photo);
    let metadataRepository = connection.getRepository(PhotoMetadata);

    // 4. first we should save a photo
    await photoRepository.save(photo);

    // 5. photo is saved. Now we need to save a photo metadata
    await metadataRepository.save(metadata);

    // 6. done
    console.log("Metadata is saved, and the relation between metadata and photo is created in the database too");

}).catch(error => console.log(error));

metadata 테이블에 photo가 속해있기 때문에 DB에 저장되는 순서는 아래와 같다.

  1. photo와 metadata를 DB와 연결하고
  2. photo 데이터를 DB에 먼저 저장한 뒤에
  3. metadata를 DB에 저장한다.

왜냐면 metadata에 photo가 속해있기 때문에 하위 entity를 새로 저장했을 때 자동으로 업데이트가 안되기 때문이다.

profile
할 수 있다는 생각이 정말 나를 할 수 있게 만들어준다.

2개의 댓글

comment-user-thumbnail
2022년 2월 27일

안녕하세요 선생님 잘지내셨는지요? typeORM에 관한 좋은 레퍼런스 찾았다 싶었더니 선생님네 벨로그였네요. 즐겨찾기 누르고 가겠읍니다 ~'^'

1개의 답글