본문 바로가기

FrontEnd

TypeOrm 스터디 : Active Record Vs Data Mapper. 그리고 Query Builder

반응형

TypeORM - Amazing ORM for TypeScript and JavaScript (ES7, ES6, ES5). Supports MySQL, PostgreSQL, MariaDB, SQLite, MS SQL Server, Oracle, WebSQL databases. Works in NodeJS, Browser, Ionic, Cordova and Electron platforms.

 

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

 

typeorm.io

오늘은 프로젝트에서 사용하기 위해 간만에 typeorm을 공부해보았다.

 

JPA같은 ORM을 사용하는 이유는 무엇일까?

선언적인 SQL과 다르게(물론 이것도 개허접이 짜면 지저분하지만) 개허접 코더는 절차적인 코드를 짠다.

따라서 보통 개발자들은 막짜도 좀 더 읽기 쉬운 SQL에 뭔가를 막 다 때려넣고 코드에서는 마이바티스 같은 SQL Mapping 프레임워크(class persistence framework)을 사용하여 데이터를 객체지향적으로 조작한다.

따라서 이는 SQL 파일이 마구마구 쌓이는 결과를 초래한다.

 

ORM은 SQL을 안쓰려고 사용한다.

객체지향, 함수형 사상과 SQL의 사상은 많이 다르다.

코드는 데이터의 본질이 아니라 비즈니스 로직. 즉 데이터의 형식을 다룬다.

또한 코드는 SQL을 직접 짜기보다는 데이터를 가져와 조작하는데 문법이 최적화되어 있다.

따라서 코드답게 SQL을 적기 위해 TypeORM을 사용한다.

이는 객체지향, 함수형 사상을 이용하여 DBMS에서 데이터를 가져오는 것이라 할 수 있다.

 

 

Active Record vs Data Mapper

TypeORM - Amazing ORM for TypeScript and JavaScript (ES7, ES6, ES5). Supports MySQL, PostgreSQL, MariaDB, SQLite, MS SQL Server, Oracle, WebSQL databases. Works in NodeJS, Browser, Ionic, Cordova and Electron platforms.

 

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

 

typeorm.io

 

TypeORM에서 DB에서 데이터를 가져오는 방법은 제목과 같이 3가지가 있다.

만약 JPA를 안다면, 먼저 Active Record 패턴과 Repository 패턴. 그 다음 QueryDSL을 배울 것이다.

해당 내용들을 배워보자.

 

 

Active Record 패턴

모델(Entity) 내에 모든 쿼리 메서드를 정의하고 모델 메서드를 사용하여 CRUD한다.
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;

}
// Active Record 저장.
const user = new User();
user.firstName = "Timber";
user.lastName = "Saw";
user.isActive = true;
await user.save();

// Active Record 삭제
await user.remove();

// Read
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" });

해당 패턴의 장점은 단순함이다.

BaseEntity 상속을 통해 기본 메소드가 자동으로 만들어진다는건 장점이다.

단순해서 유지보수가 쉽다는게 포인트이다.

단점은 단순한 대신 entity가 엄청 커질 수 있다. 메서드를 쪼개담기가 어렵기 때문이다.

Data Mapper pattern

 

엔터티는 속성만 남겨두고, Repository라는 클래스를 만들어 쿼리 메소드를 정의한다.

 

// 더미 엔터티
import {Entity, PrimaryGeneratedColumn, Column} from "typeorm";

@Entity()
export class User {

    @PrimaryGeneratedColumn()
    id: number;

    @Column()
    firstName: string;

    @Column()
    lastName: string;

    @Column()
    isActive: boolean;

}

const userRepository = connection.getRepository(User);

// 저장
const user = new User();
user.firstName = "Timber";
user.lastName = "Saw";
user.isActive = true;
await userRepository.save(user);

// 삭제
await userRepository.remove(user);

// 읽기
const users = await userRepository.find({ skip: 2, take: 5 });
const newUsers = await userRepository.find({ isActive: true });
const timber = await userRepository.findOne({ firstName: "Timber", lastName: "Saw" });

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();
    }

}

// 커스텀 엔터티 사용 방법
const userRepository = connection.getCustomRepository(UserRepository);
const timber = await userRepository.findByName("Timber", "Saw");

Active Record와 다르게 Repository에 메서드들을 담아두었다.

Active Record의 기본 매서드도 제공한다.

Repository를 여러개 만들 수 있으므로 유지보수성에 장점이 있다.

 

Query Builder

데이터 매퍼의 메소드 내에서 createQueryBuilder를 발견했는가?

QueryBuilder는 TypeORM의 가장 강력한 기능 중 하나입니다. 우아하고 편리한 구문을 사용하여 SQL 쿼리를 작성하고 실행하고 자동으로 변환된 엔터티를 가져올 수 있습니다.

라고 한다.

즉 복잡한 쿼리를 함수형으로 작성할 수 있게 해준다.

QueryDSL과 비슷한 녀석이라 볼 수 있다.

하지만 JS의 문법이 좀더 엘레건트 하므로 더 깔끔하게 사용할 수 있다.

 

이 아래를 이해하려면 TypeORM의 Relations에 대해 이해해야 한다. 

// 가장 간단한 예제
const firstUser = await connection
    .getRepository(User)
    .createQueryBuilder("user")
    .where("user.id = :id", { id: 1 })
    .getOne();
    
    
/**

SQL : 
SELECT
    user.id as userId,
    user.firstName as userFirstName,
    user.lastName as userLastName
FROM users user
WHERE user.id = 1

Result : 
User {
    id: 1,
    firstName: "Timber",
    lastName: "Saw"
}
**/

SQL을 작성하는게 더 편한가? 코드로 적는게 더 편한가?

나는 자연스러운 순서인 왼쪽에서 오른쪽, 위쪽에서 아래쪽으로 정리되며, 자동왼성이 잘되는 코드가 더 가독성이 좋은것 같다.

 

SQL도 따로 작성할 필요도 없으니 작업이 단순해진다.

 

쿼리빌더에 대한 자세한 내용을 다음 포스팅에 정리할 예정이다.

반응형