[TypeORM]


ORM 이란?


Object Relational Mapping, 객체-관계 매핑

  • 객체와 디비의 데이터를 자동으로 매핑해주는 것을 말한다.

ORM 의 장점?


ORM의 장점은 Persistent Layer를 추상화하여, 애플리케이션 레벨에서 DBMS 종속성을 제거할 수 있다는 것이다.

ORM의 단점?


복잡한 쿼리를 작성하는 데는 적합하지 않다. (JPQL or QueryDSL 활용)

생각했던 대로 쿼리가 나가지 않는다. (n+1 문제)

ORM과 DBMS 둘 다 높은 러닝 커브가 있다. 즉 학습 부하가 증가한다.

DBMS에 대한 전문지식은 BE 엔지니어로서 반드시 필요한 것이다.

ORM에 대한 지식은 라이브러리 Specific하고, 안쓰는 프로젝트도 많아서 필수적인 것은 아니다.

typeORM


typescript, javascript 기반의 ORM 라이브러리이다.

typeORM에는 2가지 사용 패던이 있다.

Active Record 패턴


BaseEntity를 상속받아 Entity를 구성한다.

또한, 내부에 static method로 QueryBuilder를 통해 Custom Query를 만들 수 도 있다.

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

@Entity()
export class User extends BaseEntity {
    @PrimaryGeneratedColumn()
    id: number

    @Column()
    firstName: string

    @Column()
    lastName: string

    @Column()
    isActive: boolean

		static findByName(firstName: string, lastName: string) {
        return this.createQueryBuilder("user")
            .where("user.firstName = :firstName", { firstName })
            .andWhere("user.lastName = :lastName", { lastName })
            .getMany();
    }
}
// example how to save AR entity
const user = new User();
user.firstName = "Timber";
user.lastName = "Saw";
user.isActive = true;
await user.save();

// example how to remove AR entity
await user.remove();

// example how to load AR entities
const users = await User.find({ skip: 2, take: 5 });
const newUsers = await User.find({ isActive: true });
const timber = await User.findOne({ firstName: "Timber", lastName: "Saw" });

Data Mapper 패턴


Data Mapper는 Entity를 멍청하게 만든다.

그리고 Repository Layer를 따로 두고 그곳에서 Query를 생성하여 개발한다.

기본적인 쿼리는 자동으로 생성되어 사용가능하다.

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

@Entity()
export class User {

    @PrimaryGeneratedColumn()
    id: number;

    @Column()
    firstName: string;

    @Column()
    lastName: string;

    @Column()
    isActive: boolean;

}
import {EntityRepository, Repository} from "typeorm";
import {User} from "../entity/User";

@EntityRepository()
export class UserRepository extends Repository<User> {

    findByName(firstName: string, lastName: string) {
        return this.createQueryBuilder("user")
            .where("user.firstName = :firstName", { firstName })
            .andWhere("user.lastName = :lastName", { lastName })
            .getMany();
    }

}

Nest.js에서는 보통 Data Mapper 패턴을 사용하여 개발한다. Nest.js 공식문서에서 Data Mapper 방식으로 설명한다. (Nest.js의 구조랑도 잘 어울린다)

EntityManager


위에 Data Mapper의 Repository가 사실 EntityManager이다

아래와 같이 Entity를 통해 Repository를 받아 쿼리를 실행할 수 있다.

import { DataSource } from "typeorm"
import { User } from "./entity/User"

const myDataSource = new DataSource(/*...*/)
const user = await myDataSource.manager.findOneBy(User, {
    id: 1,
})
user.name = "Umed"
await myDataSource.manager.save(user)

Repository를 사실 아래와 같이 EntityManager에서 특정 Entity로 제한을 두는 방식이다.

import { User } from "./entity/User"

const userRepository = dataSource.getRepository(User)
const user = await userRepository.findOneBy({
    id: 1,
})
user.name = "Umed"
await userRepository.save(user)

QueryBuilder


TypeORM 공식문서에서 앚

SQL Query를 생성할 수 있도록 도와준다.

const firstUser = await dataSource
    .getRepository(User)
    .createQueryBuilder("user")
    .where("user.id = :id", { id: 1 })
    .getOne()
...
.where('user.linkedSheep = :id', { id: sheepId })
.andWhere('user.linkedCow = :id', { id: cowId });
//Error!!!! id가 겹침

.where('user.linkedSheep = :sheepId', { sheepId })
.andWhere('user.linkedCow = :cowId', { cowId });
//이렇게 개발하자