2025.3.22 토요일의 공부기록
- Prisma ORM은 모델 간 관계를 선언할 때, 데이터베이스에 존재하지 않는 가상 필드를 제공해 더 직관적으로 데이터를 다룰 수 있도록 한다.
- 실제 데이터베이스에서는 @relation(fields: [userId], references: [id])와 같은 외래키 설정을 통해 참조 무결성을 유지하며, 설정 후에는 반드시 마이그레이션을 수행해야 한다.
- 사용자 삭제 시 연결된 데이터 처리 방식을 결정하는 referential actions를 적절히 지정하면 데이터베이스 무결성을 유지하면서 안정적인 애플리케이션 운영이 가능하다.
Prisma의 관계(Relations) 란 두 모델 간의 연결을 나타낸다. 데이터베이스 관점에서는 외래키를 통해 모델 간의 관계를 나타내지만, Prisma에서는 더 쉽고 직관적인 방법으로 이 관계를 정의할 수 있다.
예를 들어, 한 명의 사용자가 여러 개의 블로그 게시물이나 여러 개의 토큰을 가질 수 있는 경우, 이는 일대다(one-to-many) 관계로 정의할 수 있다.
Prisma에서 지원하는 주요 관계는 아래와 같다.
일대일 (one-to-one)
예) 사용자(User) ↔ 프로필(Profile)
일대다 (one-to-many)
예) 사용자(User) ↔ 블로그 게시물(Post)
다대다 (many-to-many)
예) 게시물(Post) ↔ 태그(Tag)
아래는 사용자(User)가 여러 개의 SMS 인증 토큰(SMSToken)을 갖는 일대다(one-to-many) 관계의 예시이다.
schema.prisma
파일에 관계 설정을 하면 다음과 같다.
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "sqlite"
url = env("DATABASE_URL")
}
model User {
id Int @id @default(autoincrement())
username String @unique
email String? @unique
password String?
phone String? @unique
github_id String? @unique
avatar String?
created_at DateTime @default(now())
updated_at DateTime @updatedAt
SMSToken SMSToken[]
}
model SMSToken {
id Int @id @default(autoincrement())
token String @unique
created_at DateTime @default(now())
updated_at DateTime @updatedAt
user User @relation(fields: [userId], references: [id])
userId Int
}
SMSToken
필드:SMSToken SMSToken[]
user
와 userId
필드:user User @relation(fields: [userId], references: [id])
userId Int
user
: Prisma 수준에서 관계를 설정하는 가상 필드이다.userId
: 실제 데이터베이스에서 사용되는 외래키 필드로, User 모델의 id
와 연결된다.@relation(fields: [userId], references: [id])
: 어떤 필드가 외래키인지, 그리고 어떤 필드를 참조하는지를 명확히 설정해준다.Prisma 관계(Relations) 공식 문서 바로가기
Prisma Client를 사용하면 간편하게 관계 데이터를 조회할 수 있다. 예를 들어 특정 사용자(id:1)가 가진 토큰을 조회하는 코드이다.
import { PrismaClient } from "@prisma/client";
const db = new PrismaClient();
async function test() {
const token = await db.sMSToken.findUnique({
where: {
id: 1,
},
include: {
user: true,
},
});
console.log(token);
}
test();
export default db;
관계를 설정하거나 변경한 후에는 마이그레이션을 통해 데이터베이스에 반영해야 한다.
npx prisma migrate dev
이 명령어를 통해 설정된 관계가 데이터베이스 구조에 안전하게 반영된다.
Prisma를 사용할 때 VSCode의 Prisma 익스텐션을 활용하면 코드를 작성할 때 자동 완성, 문법 강조 및 코드 포맷팅 등 여러 가지 편리한 기능을 활용할 수 있다.
Prisma 익스텐션을 설치한 뒤, 자동 완성을 활성화하기 위해 다음 설정을 추가한다.
cmd + shift + p
(Windows는 ctrl + shift + p
)를 누르고, "Open Settings(JSON)" 명령어를 입력하여 JSON 설정파일을 연다."[prisma]": {
"editor.defaultFormatter": "Prisma.prisma"
}
Referential Actions(참조 액션) 은 두 모델 간 관계가 설정된 상황에서, 참조되는 레코드가 삭제되거나 변경될 때 어떻게 처리할지를 지정하는 옵션이다.
예를 들어, 특정 사용자가 삭제될 때 그 사용자와 연관된 게시물이나 토큰을 어떻게 처리할지를 결정할 수 있다. 이는 데이터 무결성을 유지하고 데이터베이스의 일관성을 보호하는 데 매우 중요한 개념이다.
Prisma는 다음 다섯 가지의 referential actions를 지원한다.
아래는
onDelete
를 기준으로 설명하지만,onUpdate
에도 동일하게 적용된다.
Cascade
참조된 레코드를 삭제하면, 연결된 레코드도 자동으로 삭제된다.
예: 사용자를 삭제하면 사용자의 게시물도 함께 삭제된다.
Restrict
참조된 레코드와 연결된 레코드가 존재하면 삭제를 방지한다.
예: 게시물이 존재하는 사용자는 삭제할 수 없다.
NoAction
기본적으로는 Restrict와 유사하지만 데이터베이스에 따라 정확한 동작이 다를 수 있다. 일반적으로는 Restrict와 같은 효과를 가진다.
SetNull
참조된 레코드를 삭제하면 연결된 필드를 자동으로 NULL 값으로 설정한다. 단, 해당 필드가 optional(선택적)일 때만 가능하다.
예: 사용자 계정이 삭제되면 연결된 게시물의 작성자 필드가 NULL이 된다.
SetDefault
참조 필드에 기본값이 지정되어 있을 때, 참조된 레코드를 삭제하면 해당 필드의 값을 기본값으로 설정한다.
model User {
id Int @id @default(autoincrement())
username String @unique
email String? @unique
password String?
phone String? @unique
github_id String? @unique
avatar String?
created_at DateTime @default(now())
updated_at DateTime @updatedAt
SMSToken SMSToken[]
}
model SMSToken {
id Int @id @default(autoincrement())
token String @unique
created_at DateTime @default(now())
updated_at DateTime @updatedAt
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
userId Int
}
onDelete: Cascade
를 설정하여 사용자가 삭제될 때 그 사용자가 가진 모든 SMSToken
레코드도 자동으로 함께 삭제되도록 한다.userId
필드는 사용자의 기본 키(id
)를 참조하는 외래키로, 필수적으로 존재해야 하므로 선택적(Optional)이 아닌 필수 필드로 설정된다. 따라서 Cascade
를 사용할 때는 일반적으로 참조 필드가 필수(non-optional)로 설정된다.Prisma에서 참조 액션을 설정하지 않으면 데이터베이스 시스템의 기본 설정을 따른다. 대부분의 데이터베이스 시스템에서 기본적으로는 Restrict
또는 NoAction
이 설정되어 있어, 연결된 데이터가 존재할 때는 삭제가 불가능하게 된다.
안정적인 애플리케이션 운영을 위해서는 참조 액션을 명시적으로 설정하는 것이 좋다.
상황 | 추천 referential action |
---|---|
사용자 삭제 시 사용자 게시물을 삭제해야 하는 경우 | onDelete: Cascade |
게시물이 존재하면 사용자를 삭제하지 않아야 하는 경우 | onDelete: Restrict |
사용자 삭제 시 게시물은 남기되 작성자만 NULL로 설정 | onDelete: SetNull |
이렇게 각 상황에 맞게 referential actions을 선택하여 데이터베이스 무결성을 유지할 수 있다.