
이 섹션에서는 Prisma를 이용한 애플리케이션의 데이터 무결성을 확보하고, 복잡한 통계를 처리하며, 개발 환경을 효율적으로 구축하는 고급 활용법을 다룹니다.
5.1. 데이터 트랜잭션 (Transactions)
데이터베이스 트랜잭션(Transaction)은 여러 데이터베이스 작업을 하나의 원자적인 단위로 묶어 처리합니다. 이는 원자성(Atomicity)을 보장하여, 모든 작업이 성공하거나(Commit), 하나라도 실패하면 모든 작업이 취소(Rollback)되도록 합니다.
Prisma에서는 두 가지 방식의 트랜잭션을 제공합니다.
🔗 1. Interactive Transactions (대화형 트랜잭션)
여러 복잡하고 종속적인 쿼리를 유연하게 순차적으로 실행해야 할 때 사용합니다.
async transferFunds(fromUserId: number, toUserId: number, amount: number) {
// $transaction을 사용하여 쿼리 함수를 전달합니다.
return this.prisma.$transaction(async (tx) => {
// 1. 출금: 잔액 확인 및 차감
const sender = await tx.user.update({
where: { id: fromUserId },
data: { balance: { decrement: amount } },
});
// 2. 잔액 부족 시 롤백 (임의의 에러 발생)
if (sender.balance < 0) {
throw new Error('잔액이 부족합니다.');
}
// 3. 입금: 상대방 잔액 증가
const receiver = await tx.user.update({
where: { id: toUserId },
data: { balance: { increment: amount } },
});
return { sender, receiver };
}); // 이 블록 내 모든 쿼리는 'tx' 클라이언트를 사용하며 하나의 트랜잭션으로 실행됩니다.
}
📋 2. Batch Transactions (배치 트랜잭션)
종속성 없이 여러 개의 독립적인 쿼리를 순서대로 한 번에 실행해야 할 때 사용합니다.
async createUsersAndPosts(userData, postData) {
// 쿼리 목록을 배열로 전달
const result = await this.prisma.$transaction([
this.prisma.user.create(userData),
this.prisma.post.create(postData),
// ... 다른 쿼리
]);
// 쿼리 중 하나라도 실패하면 전체 롤백됩니다.
return result;
}
5.2. 집계 및 그룹화
Prisma의 집계(Aggregation) 기능을 사용하여 데이터베이스 수준에서 통계 정보를 효율적으로 계산할 수 있습니다.
📊 단일 집계 (_count, _sum, _avg, _min, _max)
특정 모델 전체 또는 필터링된 결과에 대해 통계를 계산합니다.
async getPostStats() {
const stats = await this.prisma.post.aggregate({
_count: {
_all: true, // 전체 게시물 수
published: true, // 발행된 게시물 수 (Boolean 필드)
},
_avg: {
views: true, // 평균 조회수
},
_max: {
createdAt: true, // 가장 최근 게시물 생성일
},
where: {
// 집계를 위한 필터링 조건
published: true,
},
});
// 결과 접근: stats._count._all, stats._avg.views
return stats;
}
🧑🤝🧑 그룹화 (groupBy)
특정 필드를 기준으로 데이터를 그룹화하고 각 그룹에 대한 집계를 수행합니다 (SQL의 GROUP BY).
async countPostsPerAuthor() {
const result = await this.prisma.post.groupBy({
by: ['authorId'], // authorId를 기준으로 그룹화
_count: {
_all: true, // 각 authorId 별 게시물 수 계산
},
having: {
// 그룹화된 결과에 대한 필터링 (예: 게시물이 3개 이상인 작성자만)
_count: {
_all: { gt: 3 },
},
},
});
return result;
/*
결과 예시:
[
{ authorId: 1, _count: { _all: 5 } },
{ authorId: 2, _count: { _all: 2 } },
]
*/
}
5.3. 데이터 시딩 (Seeding)
데이터 시딩(Seeding)은 개발 및 테스트 환경에서 애플리케이션 실행에 필요한 초기 데이터를 데이터베이스에 주입하는 과정입니다.
⚙️ 시딩 스크립트 작성
- package.json에서 prisma 블록에 seed 스크립트를 정의합니다.
// package.json
"prisma": {
"seed": "ts-node prisma/seed.ts"
},
// ...
"scripts": {
"prisma:seed": "npm run prisma:migrate:dev -- --create-only && npm run prisma:seed"
}
- prisma/seed.ts 파일에 시딩 로직을 작성합니다.
// prisma/seed.ts
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
async function main() {
// 이미 존재하는 데이터 삭제 (선택 사항)
await prisma.post.deleteMany({});
await prisma.user.deleteMany({});
// 1. 사용자 생성
const alice = await prisma.user.create({
data: {
email: 'alice@prisma.io',
name: 'Alice',
},
});
// 2. 게시물 생성 및 사용자 연결
await prisma.post.create({
data: {
title: 'Prisma 고급 활용',
content: '트랜잭션에 대해 알아보자.',
authorId: alice.id,
published: true,
},
});
console.log('✨ Seeding complete.');
}
main()
.catch((e) => {
console.error(e);
process.exit(1);
})
.finally(async () => {
await prisma.$disconnect();
});
🚀 시딩 실행
마이그레이션 실행 시 --create-only 플래그 없이 실행하면 시딩이 자동으로 수행됩니다.
# 마이그레이션 실행 시 시딩 스크립트가 자동으로 실행됩니다.
npx prisma migrate dev
# 또는 명시적으로 시딩만 실행
npx prisma db seed
5.4. 에러 처리 및 로깅 전략
🛑 에러 처리 (Prisma Client Exception)
Prisma는 데이터베이스 제약 조건 위반 등에서 발생하는 에러를 PrismaClientKnownRequestError 객체로 반환합니다. NestJS에서는 이를 인터셉터나 예외 필터(Exception Filter)를 통해 처리해야 합니다.
가장 흔한 에러 유형은 에러 코드(PXXXX)를 통해 구분됩니다.
| 코드 | 설명 | NestJS 처리 전략 |
| P2002 | 고유 제약 조건 위반 (Unique Constraint) | ConflictException (409) |
| P2025 | 필요한 레코드를 찾을 수 없음 (findUnique 실패) | NotFoundException (404) |
| P2003 | 외래 키 제약 조건 위반 (Foreign Key) | BadRequestException (400) |
NestJS 예외 필터 예시:
// Exception Filter 내부 (예시)
import { Prisma } from '@prisma/client';
if (exception instanceof Prisma.PrismaClientKnownRequestError) {
if (exception.code === 'P2002') {
// P2002: Unique 제약 조건 위반 (e.g., 이메일 중복)
throw new ConflictException('이미 존재하는 데이터입니다.');
}
if (exception.code === 'P2025') {
// P2025: 레코드를 찾을 수 없음
throw new NotFoundException('요청한 리소스를 찾을 수 없습니다.');
}
}
// ...
📝 로깅 전략
Prisma Client를 초기화할 때 log 옵션을 설정하여 실행되는 쿼리를 기록할 수 있습니다 (2.1절 PrismaService 참고).
export class PrismaService extends PrismaClient implements OnModuleInit {
constructor() {
super({
log: [
{ level: 'query', emit: 'event' }, // 실행 쿼리 로깅
{ level: 'error', emit: 'stdout' }, // 에러는 표준 출력
],
});
}
// ...
}
고급 로깅 (이벤트 리스너):
emit: 'event'로 설정하면 PrismaService에서 $on 메서드를 사용하여 쿼리 이벤트를 수신하고, 이를 NestJS의 로거(Logger)와 통합하여 사용할 수 있습니다.
// PrismaService 내부에 추가
constructor(private readonly logger: Logger) { // NestJS Logger 주입
super({
log: [{ level: 'query', emit: 'event' }],
});
}
onModuleInit() {
this.$on('query', (event) => {
this.logger.log(`Query: ${event.query}`, 'PrismaQuery');
this.logger.log(`Params: ${event.params}`, 'PrismaParams');
this.logger.log(`Duration: ${event.duration}ms`, 'PrismaDuration');
});
this.$connect();
}
'Nest.js를 배워보자 > 15. NestJS & Prisma 완벽 가이드' 카테고리의 다른 글
| 🚀 7부.NestJS와 Prisma로 Raw SQL 쿼리 사용하기 (rowsql) (0) | 2025.12.03 |
|---|---|
| 🚢 6부. 실전 배포 환경 구성 (0) | 2025.12.03 |
| 🌐 4부. 관계형 데이터 쿼리 마스터하기 (0) | 2025.12.03 |
| 🔍 3부. CRUD 기본 및 복합 쿼리 (0) | 2025.12.03 |
| 🛠️ 2부. NestJS 모듈 시스템에 Prisma 통합하기 (0) | 2025.12.03 |