Prisma ORM: 기본 개념과 동작 원리

2024. 7. 23. 18:16ORM

Prisma ORM: 기본 개념과 동작 원리

 

Prisma ORM이란?

Prisma는 JavaScript와 TypeScript 애플리케이션에서 데이터베이스와 상호작용을 쉽게 할 수 있게 해주는 오픈소스 ORM(Object-Relational Mapping)입니다. ORM은 데이터베이스의 테이블 구조와 프로그래밍 언어의 객체를 연결하여, 개발자가 데이터베이스 조작을 객체 지향적인 코드로 구현할 수 있게 합니다.

Prisma의 주요 장점은 타입 안전성자동화된 쿼리 빌더입니다. 이를 통해 SQL 쿼리를 직접 작성하지 않고도 데이터베이스 작업을 수행할 수 있으며, 컴파일 단계에서 타입 오류를 잡아낼 수 있어 코드의 안정성을 높입니다.

Prisma ORM의 구성 요소

다음과 같은 구성 요소로 이루어져 있습니다.

Prisma Client: 데이터베이스와 상호작용하기 위한 타입 안전한 쿼리 빌더
Prisma Migrate: 데이터베이스 구조(스키마)를 변경할 때 사용하는 마이그레이션 도구
Prisma Studio: 데이터베이스의 데이터를 확인하고 수정할 수 있는 GUI 도구, 이 부분은 오픈소스가 아님

Prisma Client는 ORM(Object-Relational Mapping) 기능을 제공하여 직접 SQL 쿼리를 작성할 필요 없이 코드로 데이터베이스 작업을 처리할 수 있습니다. 덕분에 데이터 조회, 생성, 업데이트, 삭제 같은 작업을 객체 지향적인 방식으로 더 간결하고 안전하게 수행할 수 있습니다.

예를 들어, 데이터베이스에서 유저 정보를 가져오는 SQL 쿼리를 작성할 필요 없이, Prisma Client의 메서드를 사용해 다음과 같이 간단하게 데이터를 가져올 수 있습니다:

const user = await prisma.user.findUnique({
  where: { id: 1 },
});

이렇게 코드에서 데이터베이스 객체에 바로 접근할 수 있기 때문에, 개발을 훨씬 더 직관적이고 효율적으로 할 수 있습니다.

relations

Prisma 스키마는 강력한 데이터 모델링 기능을 가지고 있습니다. 예를 들어, "Prisma-lelvel"의 관계 필드를 정의할 수 있어 Prisma 클라이언트 API에서 relations 작업을 더 쉽게 할 수 있습니다.

관계는 프리즈마 스키마에서 두 모델 간의 연결입니다. 예를 들어, 한 사용자가 많은 블로그 글을 가질 수 있으므로 사용자와 게시물 사이에는 일대다 관계가 있습니다. 다음 프리즈마 스키마는 사용자 모델과 게시물 모델 간의 일대다 관계를 정의합니다.

model User {
  id    Int    @id @default(autoincrement())
  posts Post[]
}

model Post {
  id       Int  @id @default(autoincrement())
  author   User @relation(fields: [authorId], references: [id])
  authorId Int // relation scalar field  (used in the `@relation` attribute above)
}
두 개의 관계 필드: User의 posts와 Post의 author.
관계 필드는 Prisma ORM level에서 모델 간의 연결을 정의하며 데이터베이스에는 존재하지 않습니다.
이러한 필드는 Prisma Client를 생성하는 데 사용됩니다.

Post의 authorId는 @relation 속성에서 참조하는 스칼라 필드입니다.
이 필드는 데이터베이스에 존재하며 게시물과 사용자를 연결하는 외래 키입니다.
다음 엔티티 관계 다이어그램은 관계형 데이터베이스의 User 테이블과 Post 테이블 간에 동일한 일대다 관계를 정의합니다. 관계 필드는 존재하지 않는 것을 확인할 수 있습니다.

 

Prisma ORM은 어떻게 동작하는가?

Prisma ORM 툴킷을 사용하는 모든 프로젝트는 Prisma schema로 시작합니다. Prisma schema는 개발자가 직관적인 데이터 모델링 언어로 애플리케이션 모델을 정의할 수 있게 해 줍니다. 또한 데이터베이스 연결을 포함하고, generator를 정의합니다.

//schema.prisma

datasource db { //데이터베이스와의 연결 정보를 정의
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

generator client { //Prisma Client와 같은 Prisma의 자동 생성 도구를 설정
  provider = "prisma-client-js"
}

model Post { //실제 데이터베이스 테이블을 정의하는 부분
  id        Int     @id @default(autoincrement())
  title     String
  content   String?
  published Boolean @default(false)
  author    User?   @relation(fields: [authorId], references: [id])
  authorId  Int?
}

model User {
  id    Int     @id @default(autoincrement())
  email String  @unique
  name  String?
  posts Post[]
}
  • Prisma schema는 datasoruce, generator, model 이 세 가지 요소로 구성됩니다.
  • 데이터 모델은 데이터베이스에서 데이터를 어떻게 저장할지를 정의하는 구조입니다.
  • Prisma schema에서는 모델이라는 개념을 사용해서 데이터베이스의 테이블(관계형 데이터베이스)이나 컬렉션(MongoDB)을 정의합니다.
  • Prisma Client를 사용하면, model을 통해 생성된 테이블에서 데이터를 추가하거나 조회할 수 있습니다.

 

Data Model 가져오기

모델의 컬렉션데이터 모델을 의미합니다. Prisma 스키마에서 이 데이터 모델을 가져오는 방법에는 크게 두 가지가 있습니다.

1. 데이터 모델을 수동으로 작성하고, Prisma Migrate을 사용하여 데이터베이스에 매핑
2. 데이터베이스로부터 생성하기(introspecting)

첫 번째 방법에 대해 먼저 알아보겠습니다.

데이터 모델 가져오는 방법 1:  Getting started with Prisma Migrate

Prisma ORM의 통합 데이터베이스 마이그레이션 도구인 Prisma Migrate를 사용할 때의 작업 흐름은 다음과 같습니다:

1. 'schema.prisma' 파일을 수정해 데이터 모델을 업데이트 합니다.
2. prisma migrate dev로 개발 환경의 데이터베이스에 변경사항을 적용합니다.
3. 애플리케이션 코드에서 Prisma Client를 사용하여 데이터베이스에 접근합니다.

prisma migrate dev 명령어 사용 시 다음과 같은 에러가 발생할 수 있습니다:

🔍. 에러 체크

원인:
P3005 에러는 Prisma Migrate가 스키마가 비어 있는 상태의 데이터베이스에서 마이그레이션을 적용하는 것을 기본 가정하기 때문에 발생합니다. 이미 테이블이나 데이터가 있는 경우 Prisma는 새로운 스키마를 안전하게 덮어쓸 수 없으므로 에러가 발생하게 됩니다.
특히, 이 에러는 기존 데이터베이스에 이미 데이터가 들어있는 프로덕션 환경에서 새로운 마이그레이션을 적용하려 할 때 발생할 가능성이 큽니다.

해결 방법:
방법 1: 기존 데이터베이스와 동기화 (Baseline Migration 설정)

이미 프로덕션에서 사용 중인 데이터베이스에 변경을 적용하려는 경우, Baseline Migration을 설정해야 합니다. Baseline Migration은 기존 데이터베이스 상태를 기준으로 Prisma가 마이그레이션을 처리하도록 하는 방법입니다.

npx prisma migrate resolve --applied "initial-migration"​
위 명령어는 initial-migration이라는 이름의 마이그레이션을 이미 적용된 것으로 처리하여 이후 새로운 마이그레이션을 생성하고 적용할 수 있도록 해줍니다.

방법 2: 새로운 데이터베이스로 마이그레이션
실험적인 환경이거나 테스트용으로 사용하는 데이터베이스라면, 기존 데이터베이스를 삭제하거나 초기화하여 새로운 마이그레이션을 적용할 수 있습니다. 이 경우, Prisma는 prisma migrate dev 명령어를 사용하여 새로운 데이터베이스 스키마를 생성하고, 마이그레이션을 제대로 적용할 수 있습니다.
npx prisma migrate reset​
prisma migrate reset은 데이터베이스를 초기화하고, prisma migrate dev로 새로운 마이그레이션을 적용할 수 있게 해줍니다.
주의: 이 명령은 데이터베이스의 모든 데이터를 삭제하므로 실제 데이터베이스에서 실행하지 않도록 주의해야 합니다.

* 마이그레이션 적용 후 데이터베이스 상태 점검
npx prisma migrate status​​
위 명령어는 현재 데이터베이스에 적용된 마이그레이션 상태를 확인하는 데 유용합니다. 이 명령어를 실행하면, 데이터베이스에서 실행된 마이그레이션과 아직 적용되지 않은 마이그레이션이 무엇인지 확인할 수 있습니다.

참고: migrate-baseline
⭐️ prisma migrate는 주로 개발 환경에서 사용하는 도구입니다. 개발 단계에서 데이터베이스 스키마를 반복적으로 수정하고 실험할 때 사용하기에 적합하며, 다음과 같은 이유로 프로덕션 환경에서의 사용을 권장하지 않습니다. ⭐️
  1. 안정성 문제: 프로덕션 데이터베이스에서 prisma migrate를 실행하면 예기치 않은 데이터 손실이나 스키마 충돌이 발생할 수 있습니다.
  2. 데이터 손실 위험: 개발 환경에서 스키마를 반복적으로 수정할 때는 문제가 없지만, 프로덕션 환경에서는 데이터가 중요하므로 더 신중한 마이그레이션 방법을 사용해야 합니다.
  3. 관리의 복잡성: 프로덕션 환경에서는 수동 SQL 스크립트나 prisma migrate deploy를 사용하는 것이 일반적입니다. 이는 더 세밀하게 마이그레이션을 관리하고 검증할 수 있기 때문입니다.

개발 환경에서 prisma migrate dev를 사용하여 마이그레이션을 생성하고, 프로덕션 환경에 배포할 때는 생성된 마이그레이션 파일을 prisma migrate deploy로 적용하는 방식을 추천합니다.

이제 두 번째 방법에 대해 알아보겠습니다.

데이터 모델 가져오는 방법 2: Introspecting

introspecting

Introspection workflow

Plain SQL을 사용해 데이터베이스 스키마를 수정한 후, Prisma 스키마를 업데이트하기 위해 prisma db pull 명령어를 사용합니다. 이 명령어는 데이터베이스의 현재 상태를 반영하여 Prisma 스키마 파일(schema.prisma)을 자동으로 업데이트합니다.

그 후, Prisma Client 코드가 최신 상태로 업데이트되도록 prisma generate 명령어를 실행합니다. 이 명령어는 node_modules/.prisma/client 폴더 안에 Prisma Client 코드를 재생성하여, 새로운 데이터 모델을 반영한 API를 제공하게 됩니다.

prisma db pull 명령어를 사용하면, 데이터베이스의 현재 상태를 Prisma schema 파일로 가져올 수 있습니다.
이 명령어는 데이터베이스의 구조를 Prisma schema로 반영할 때 유용합니다.

prisma db push 명령어는 Prisma schema 파일에 정의된 모델을 데이터베이스에 강제로 적용합니다.
이 명령어는 마이그레이션을 생성하는 대신, 데이터베이스 구조를 바로 업데이트하는 방식입니다. 이 명령어는 데이터베이스를 Prisma 스키마에 맞게 동기화 하며, 일반적으로 데이터가 유지됩니다. 그러나, 일부 경우에 예상치 못한 테이블 구조 변경이나 데이터 손실이 발생할 수 있으므로 중요한 데이터를 보호하려면 신중하게 진행해야 합니다.

 

Prisma Client를 이용해 데이터베이스에 Access하기

Prisma Client를 사용하려면 먼저 @prisma/client npm 패키지를 설치해야 합니다:

npm install @prisma/client
 
@prisma/client 패키지를 설치하면, prisma generate 명령이 자동으로 실행되어 Prisma 스키마를 읽고 Prisma Client 코드를 생성합니다. 기본적으로 생성된 코드는 node_modules/.prisma/client 폴더에 저장됩니다.

데이터 모델을 변경한 후에는, node_modules/.prisma/client 폴더의 코드가 업데이트되도록 Prisma Client를 수동으로 다시 생성해야 합니다:

npx prisma generate
🔍  Prisma Client code란..
Prisma ORM이 데이터베이스와 상호작용할 수 있게 해주는 JavaScript/TypeScript API로,
prisma.schema 파일에서 정의한 데이터 모델에 맞춰 자동으로 생성됩니다.
prisma generate 명령어는 이 코드를 새로 생성하여, 데이터베이스 작업을 쉽게 할 수 있도록 해줍니다.

 

Prisma Client 사용 예시

import { PrismaClient } from '@prisma/client'

const prisma = new PrismaClient()

//Retrieve all User records from the database
// Run inside `async` function
const allUsers = await prisma.user.findMany()

//Include the posts relation on each returned User object
// Run inside `async` function
const allUsers = await prisma.user.findMany({
  include: { posts: true },
})

// Filter all Post records that contain "prisma"
// Run inside `async` function
const filteredPosts = await prisma.post.findMany({
  where: {
    OR: [
      { title: { contains: 'prisma' } },
      { content: { contains: 'prisma' } },
    ],
  },
})

// Create a new User and a new Post record in the same query
// Run inside `async` function
const user = await prisma.user.create({
  data: {
    name: 'Alice',
    email: 'alice@prisma.io',
    posts: {
      create: { title: 'Join us for Prisma Day 2020' },
    },
  },
})

// Update an existing Post record
// Run inside `async` function
const post = await prisma.post.update({
  where: { id: 42 },
  data: { published: true },
})

 

이상으로 Prisma의 기본 개념과 동작 원리, 사용법에 대해 알아보았습니다.

 

참고

What is Prisma ORM? | Prisma Documentation

Relations | Prisma Documentation

ORM 이해하기 | F-Lab