icon안동민 개발노트

ORM 사용 (TypeORM, Prisma)


 Object-Relational Mapping (ORM) 도구는 데이터베이스 작업을 객체지향적으로 처리할 수 있게 해주며 TypeScript와 함께 사용하면 타입 안전성을 극대화할 수 있습니다.

 이 절에서는 TypeORM과 Prisma를 중심으로 ORM 사용법을 살펴보겠습니다.

TypeORM vs Prisma

 1. TypeORM

  • Active Record와 Data Mapper 패턴 모두 지원
  • 다양한 데이터베이스 지원
  • 복잡한 쿼리에 대한 유연성 제공

 2. Prisma

  • 스키마 중심 접근 방식
  • 강력한 타입 안전성
  • 자동 생성된 클라이언트로 간편한 사용

TypeORM과 TypeScript 통합

 TypeORM 엔티티 정의

import { Entity, PrimaryGeneratedColumn, Column, OneToMany } from "typeorm";
import { Post } from "./Post";
 
@Entity()
export class User {
    @PrimaryGeneratedColumn()
    id: number;
 
    @Column()
    name: string;
 
    @Column({ unique: true })
    email: string;
 
    @OneToMany(() => Post, post => post.author)
    posts: Post[];
}

Prisma와 TypeScript 통합

 Prisma 스키마 정의 (schema.prisma)

model User {
  id    Int     @id @default(autoincrement())
  name  String
  email String  @unique
  posts Post[]
}
 
model Post {
  id        Int     @id @default(autoincrement())
  title     String
  content   String?
  published Boolean @default(false)
  author    User    @relation(fields: [authorId], references: [id])
  authorId  Int
}

 Prisma 클라이언트 생성

npx prisma generate

관계 모델링

  1. TypeORM
@Entity()
export class Post {
    @PrimaryGeneratedColumn()
    id: number;
 
    @Column()
    title: string;
 
    @ManyToOne(() => User, user => user.posts)
    author: User;
}
  1. Prisma 관계는 스키마 파일에서 정의됩니다. (위의 예시 참조)

CRUD 작업 구현

  1. TypeORM
import { getRepository } from "typeorm";
import { User } from "./entity/User";
 
// 생성
const user = new User();
user.name = "John";
user.email = "[email protected]";
await getRepository(User).save(user);
 
// 읽기
const users = await getRepository(User).find();
 
// 수정
user.name = "Jane";
await getRepository(User).save(user);
 
// 삭제
await getRepository(User).delete(user.id);
  1. Prisma
import { PrismaClient } from '@prisma/client'
const prisma = new PrismaClient()
 
// 생성
const user = await prisma.user.create({
  data: { name: "John", email: "[email protected]" },
})
 
// 읽기
const users = await prisma.user.findMany()
 
// 수정
const updatedUser = await prisma.user.update({
  where: { id: user.id },
  data: { name: "Jane" },
})
 
// 삭제
await prisma.user.delete({ where: { id: user.id } })

마이그레이션 관리

  1. TypeORM
typeorm migration:create -n CreateUserTable
typeorm migration:run
typeorm migration:revert
  1. Prisma
npx prisma migrate dev --name init
npx prisma migrate deploy

복잡한 쿼리 구현

  1. TypeORM
const users = await getRepository(User)
  .createQueryBuilder("user")
  .leftJoinAndSelect("user.posts", "post")
  .where("user.name LIKE :name", { name: "%John%" })
  .getMany();
  1. Prisma
const users = await prisma.user.findMany({
  where: { name: { contains: "John" } },
  include: { posts: true }
});

트랜잭션 관리

  1. TypeORM
await getManager().transaction(async transactionalEntityManager => {
    const user = new User();
    user.name = "John";
    await transactionalEntityManager.save(user);
    
    const post = new Post();
    post.title = "Hello World";
    post.author = user;
    await transactionalEntityManager.save(post);
});
  1. Prisma
await prisma.$transaction(async (prisma) => {
  const user = await prisma.user.create({ data: { name: "John" } });
  await prisma.post.create({
    data: { title: "Hello World", author: { connect: { id: user.id } } }
  });
});

성능 최적화 및 N+1 문제 해결

 1. TypeORM

  • 관계를 미리 로드하여 N+1 문제 해결
const users = await getRepository(User)
  .createQueryBuilder("user")
  .leftJoinAndSelect("user.posts", "post")
  .getMany();

 2. Prisma

  • include를 사용하여 관계 데이터를 한 번에 로드
const users = await prisma.user.findMany({
  include: { posts: true }
});

Best Practices 및 선택 기준

 1. 스키마 설계

  • TypeORM : 데코레이터를 사용한 명시적인 관계 정의
  • Prisma : 스키마 파일을 통한 중앙화된 데이터 모델 관리

 2. 타입 안전성

  • TypeORM : 데코레이터와 TypeScript 클래스를 통한 타입 정의
  • Prisma : 자동 생성된 타입으로 높은 수준의 타입 안전성 제공

 3. 쿼리 복잡성

  • TypeORM : 복잡한 쿼리에 대한 높은 유연성
  • Prisma : 간단하고 직관적인 API, 복잡한 쿼리는 raw SQL 필요

 4. 마이그레이션

  • TypeORM : 세밀한 제어가 가능하지만 수동 관리 필요
  • Prisma : 자동화된 마이그레이션으로 편리하지만 유연성 제한

 5. 성능

  • TypeORM : 쿼리 최적화에 대한 세밀한 제어 가능
  • Prisma : 자동 최적화로 기본적인 성능 보장

 6. 학습 곡선

  • TypeORM : ORM 개념에 익숙한 개발자에게 친숙
  • Prisma : 간단한 API로 초보자도 쉽게 사용 가능

 7. 프로젝트 규모

  • TypeORM : 대규모, 복잡한 프로젝트에 적합
  • Prisma : 중소규모 프로젝트나 빠른 개발이 필요한 경우에 유리

 8. 데이터베이스 지원

  • TypeORM : 다양한 데이터베이스 지원
  • Prisma : 주요 관계형 데이터베이스 지원, NoSQL 지원 제한적

 TypeORM과 Prisma는 각각의 장단점이 있으며, 프로젝트의 요구사항과 팀의 경험에 따라 선택해야 합니다.

 TypeORM은 복잡한 쿼리와 세밀한 제어가 필요한 대규모 프로젝트에 적합하며, Prisma는 빠른 개발과 강력한 타입 안전성이 필요한 프로젝트에 적합합니다.

 두 ORM 모두 TypeScript와 잘 통합되어 타입 안전성을 제공하지만, Prisma의 자동 생성된 타입은 더 강력한 타입 체크를 제공합니다. 반면 TypeORM은 더 유연한 쿼리 작성과 데이터베이스 스키마 제어를 제공합니다.

 요즘은 Drizzle이라 하는 ORM이 유행하는 것 같습니다.

 다양한 ORM이 있으므로 프로젝트의 요구사항에 맞게 선택하는 것이 중요합니다.