TypeORM에서 직접 SQL을 사용하는 경우와 방법

SEUNGJUN·2024년 6월 22일

TypeORM을 사용하면 따로 SQL 쿼리문을 쓰지 않아도 되는가 라는 의문이 있다. 그럼 어떤 상황일때 쿼리문을 써주는게 좋을지 알아보자.

ORM을 사용하는 이유

1. 추상화 및 간편성

  • ORM은 데이터베이스와 상호 작용할 때 객체 지향 프로그래밍 패러다임을 사용하여 데이터를 추상화 한다. 이는 개발자가 데이터베이스와 직접 상호작용하는 대신 객체 지향 코드로 데이터를 처리할 수 있게 한다.

2. 유지 보수성

  • ORM을 사용하면 코드가 더 읽기 쉽고 유지보수하기 쉬워진다. 데이터베이스 스키마 변경 시에도 ORM은 이를 쉽게 반영할 수 있도록 도와준다.

3. 플랫폼 독립성

  • ORM은 여러 데이터베이스 시스템 간의 이식성을 높여준다. 예를 들어, 개발 초기에는 SQLite를 사용하다가 나중에 PostgreSQL로 변경할 때, ORM을 사용하면 코드 변경을 최소화할 수 있다.

4. 자동화된 데이터베이스 작업

  • ORM은 기본적인 CRUD 작업을 자동으로 처리해주어 반복적인 쿼리 작성을 줄여준다.

TypeORM을 사용해도 쿼리를 사용해야 하는 경우

1. 복잡한 조인 쿼리

직접 SQL 쿼리 (복잡한 조인)

SELECT 
    users.id, 
    users.name, 
    orders.total, 
    products.name 
FROM 
    users 
JOIN 
    orders ON users.id = orders.user_id 
JOIN 
    order_items ON orders.id = order_items.order_id 
JOIN 
    products ON order_items.product_id = products.id 
WHERE 
    users.active = true AND orders.status = 'completed';

위 쿼리는 여러 테이블을 조인하여 조건에 맞는 데이터를 조회하는 예시이다.

TypeORM에서의 복잡한 조인

import { createQueryBuilder } from 'typeorm';
import { User } from './entity/User';
import { Order } from './entity/Order';
import { OrderItem } from './entity/OrderItem';
import { Product } from './entity/Product';

const users = await createQueryBuilder('user')
    .select(['user.id', 'user.name', 'order.total', 'product.name'])
    .from(User, 'user')
    .innerJoin(Order, 'order', 'user.id = order.user_id')
    .innerJoin(OrderItem, 'orderItem', 'order.id = orderItem.order_id')
    .innerJoin(Product, 'product', 'orderItem.product_id = product.id')
    .where('user.active = :active', { active: true })
    .andWhere('order.status = :status', { status: 'completed' })
    .getMany();

위 TypeORM 코드에서 동일한 조인 작업을 수행하고 있지만, 코드가 길고 복잡해졌다.

TypeORM에서 직접 SQL 쿼리를 사용하는 방법

import { getManager } from 'typeorm';

const entityManager = getManager();
const users = await entityManager.query(`
    SELECT 
        users.id, 
        users.name, 
        orders.total, 
        products.name 
    FROM 
        users 
    JOIN 
        orders ON users.id = orders.user_id 
    JOIN 
        order_items ON orders.id = order_items.order_id 
    JOIN 
        products ON order_items.product_id = products.id 
    WHERE 
        users.active = true AND orders.status = 'completed'
`);

TypeORM에서 복잡한 조인 쿼리르 작성하려면 여러 번의 innerJoinwhere 조건을 체인 형태로 연결해야 한다. 이는 가독성을 떨어뜨리고, 유지보수를 어렵게 만들 수 있다. 반면, 직접 SQL 쿼리를 작성하면 더 간결하고 명확하게 원하는 작업을 수행할 수 있다.

2. 퍼포먼스 최적화 (대량 데이터 일괄 삽입)

ORM이 생성하는 쿼리가 비효율적일 때, 직접 최적화된 SQL 쿼리를 사용하는 것이 필요할 수 있다. 특히 대량의 데이터를 일괄 삽입해야 하는 경우이다.

직접 SQL 쿼리 (대량 삽입)

INSERT INTO users (name, email)
VALUES 
    ('John Doe', 'john@example.com'),
    ('Jane Smith', 'jane@example.com'),
    ('Alice Brown', 'alice@example.com');

TypeORM에서 직접 SQL 쿼리 사용

import { getRepository } from 'typeorm';
import { User } from './entity/User';

const userRepository = getRepository(User);

const users = [
    { name: 'John Doe', email: 'john@example.com' },
    { name: 'Jane Smith', email: 'jane@example.com' },
    { name: 'Alice Brown', email: 'alice@example.com' }
];

await userRepository.save(users);

두 코드 모두 여러 개의 사용자 데이터를 데이터베이스에 삽입하는 역할을 하지만, 실제 동작 방식에는 차이가 있다.

비교

  1. 엔티티 매핑 및 데이터 검증

    • TypeORM의 save 메서드 : 엔티티 매핑 및 데이터 검증을 자동으로 수행한다. 데이터베이스 테이블 구조와 TypeScript 코드 간의 일관성을 유지한다.
    • 직접 SQL 쿼리 : 엔티티 매핑이 없으므로 데이터 검증 및 매핑을 수동으로 관리해야 한다. 테이블 구조가 변경되면 쿼리를 직접 수정해야 한다.
  2. 성능

    • TypeORM의 save 메서드 : 성능 최적화를 위해 여러 레코드를 한 번에 처리할 수 있지만, 내부적으로 여러 INSERT 쿼리를 실행할 수 있다.
    • 직접 SQL 쿼리 : 성능이 매우 최적화되어 있으며, 한 번의 쿼리를 여러 레코드를 삽입하여 네트워크 오버헤드를 줄인다.

직접 SQL 쿼리를 사용한 대량 삽입

import { getManager } from 'typeorm';

const entityManager = getManager();
await entityManager.query(`
    INSERT INTO users (name, email)
    VALUES 
        ('John Doe', 'john@example.com'),
        ('Jane Smith', 'jane@example.com'),
        ('Alice Brown', 'alice@example.com');
`);

결론

TypeORM의 save 메서드는 편리하고 대부분의 사용 사례에서 잘 동작하지만, 대량 데이터를 삽입 할 때는 성능이 떨어질 수 있다. 이 경우 직접 SQL 쿼리를 사용하여 일괄 삽입하는 것이 더 효율적일 수 있다.

profile
RECORD DEVELOPER

0개의 댓글