7편. Prisma 트랜잭션과 데이터 정합성: 실무 설계와 구현
📚 목차
1. 트랜잭션의 역할과 데이터 정합성 전략
2. Prisma 트랜잭션의 종류와 사용 시점
3. 트랜잭션 고급 제어 옵션과 성능 최적화 전략
4. 트랜잭션 기반 다중 CRUD 처리 실습

📂 [GitHub 예시 코드 보러가기] (https://github.com/cericube/nodejs-tutorials) /Prisma
1. 트랜잭션의 역할과 데이터 정합성 전략
트랜잭션(Transaction)은 여러 개의 데이터베이스 작업을 하나의 논리적 작업 단위로 묶어 처리하는 메커니즘입니다.
즉, 다음 조건을 보장합니다.
▸ 모두 성공하면 전부 반영(Commit)
▸ 하나라도 실패하면 전부 취소(Rollback)
관계형 데이터베이스(PostgreSQL 포함)에서 트랜잭션은 오래전부터 사용되어 온 핵심 개념이며, Prisma 역시 내부적으로 데이터베이스 트랜잭션을 그대로 활용합니다.
🔷 왜 트랜잭션이 필요한가?
데이터베이스 작업은 "전부 성공하거나, 전부 실패(All or Nothing)"해야 합니다. 트랜잭션 없이 작성된 아래 코드는 전형적인 정합성 파괴의 원인이 됩니다.
// ❌ 위험한 코드: 정합성 보장 불가
const user = await prisma.user.create({ data: { email: "a@test.com" } });
// 만약 이 사이에서 서버가 꺼지거나 아래 쿼리가 실패한다면?
await prisma.profile.create({ data: { userId: user.id, bio: "Hello" } });
// 결과: Profile 없는 User만 남음 (DB 미상태 불일치)
user.create가 이미 커밋된 상태에서 profile.create가 실패하면, User만 존재하고 Profile이 없는 데이터 정합성이 깨진 불완전한 상태가 됩니다.
🔷 2. [최적화 Tip] Nested Writes (중첩 쓰기)
Prisma 7에서는 단순 연관 데이터 생성을 위해 $transaction을 명시적으로 호출하기보다 Nested Writes를 우선 권장합니다.
이는 단일 네트워크 호출로 원자성을 보장하며 성능이 더 뛰어납니다.
// 효율적인 방식: 단일 쿼리로 1:1 관계 생성
await prisma.user.create({
data: {
email: "a@test.com",
profile: {
create: { bio: "Hello" }
}
}
});
2. Prisma 트랜잭션의 종류와 사용 시점
🔷 배열 기반 트랜잭션 (Batch Transaction)
Prisma에서 제공하는 가장 간단하고 직관적인 트랜잭션 방식입니다.
여러 개의 독립적인 쿼리를 하나의 배열로 묶어서 실행하고 싶을 때 사용합니다.
await prisma.$transaction([
prisma.user.create({ ... }),
prisma.profile.create({ ... }),
]);
🔸 원자성 보장: 배열 안의 모든 쿼리는 하나의 작업 단위로 묶입니다.
🔸 자동 롤백: 쿼리 중 단 하나라도 실패하면, 실행되었던 모든 작업이 취소(Rollback)되어 데이터의 일관성을 유지합니다.
✔️ 특징
▸ 가독성: 코드가 간결하고, '여러 작업을 하나로 묶었다'는 의도가 명확히 보입니다.
▸ 단순성: 복잡한 비즈니스 로직 없이, 단순히 데이터를 생성·수정·삭제하는 묶음 작업에 최적화되어 있습니다.
▸ 조건부 실행 불가: 첫 번째 쿼리의 결과값을 받아서 if문으로 분기를 태우거나, 중간에 값을 가공하여 다음 쿼리에 사용할 수 없습니다.
▸ 유연성 부족: try/catch나 반복문 같은 제어 로직을 트랜잭션 내부에서 사용할 수 없습니다.
✔️ 예제: 사용자(User)와 프로필(Profile) 동시 생성
COMMIT 예시
async function createUserWithProfile() {
console.log('--- 사용자와 프로필 동시 생성 ---');
const [user, profile] = await prisma.$transaction([
prisma.user.create({
data: {
email: 'trans@example.com',
displayName: '트랜잭션 사용자',
},
}),
//
prisma.profile.create({
data: {
bio: '트랜잭션 생성자 입니다.',
user: {
connect: { email: 'trans@example.com' },
},
},
}),
]);
console.log('--- 사용자와 프로필 동시 생성 종료---');
}

ROLLBACK 예시
// 롤백 테스트
async function createUserWithProfile() {
const [user, profile] = await prisma.$transaction([
prisma.user.create({
data: {
email: 'trans_fail@example.com',
displayName: '트랜잭션 실패 사용자',
},
}),
//
prisma.profile.create({
data: {
bio: '트랜잭션 실패 생성자 입니다.',
user: {
connect: { email: 'trans_failfail@example.com' },
},
},
}),
]);
console.log('--- 사용자와 프로필 동시 생성 종료---');
}

🔷 대화형 트랜잭션 (Interactive Transactions)
Prisma 7 기준으로 실무에서 가장 권장되는 표준 방식입니다.
단순히 쿼리를 나열하는 것이 아니라, 트랜잭션 도중에 데이터를 조회하거나 조건에 따라 로직을 결정해야 할 때 사용합니다.
await prisma.$transaction(async (tx) => {
// 이 블록 안에서 'tx' 객체를 통해 쿼리를 실행합니다.
});
🔸 tx (Transaction Client): 트랜잭션 범위 안에서만 유효한 전용 Prisma Client입니다.
🔸안전한 스코프: 블록 안의 코드가 모두 성공적으로 완료되면 자동으로 Commit 되고, 중간에 에러가 발생하면 자동으로 Rollback 됩니다.
✔️ 왜 이 방식이 실무 표준인가요? (핵심 장점)
▸ 자유로운 조건 분기 (if): 쿼리 결과에 따라 다음 작업을 진행할지, 혹은 에러를 던져 중단할지 결정할 수 있습니다.
▸ 반복문 활용 (for, map): 여러 데이터를 루프 돌며 처리해야 하는 복잡한 로직도 트랜잭션 안에서 안전하게 처리가 가능합니다.
▸ 세밀한 예외 처리 (try/catch): 로직 중간에 발생하는 예외를 직접 핸들링할 수 있어 안정성이 높습니다.
▸ 연쇄 작업 가능: 이전 쿼리에서 생성된 데이터(예: 생성된 ID값)를 다음 쿼리의 입력값으로 바로 사용할 수 있습니다.
✔️ 예제: 유저 생성 후 결과에 따른 로직 제어
async function createUserWithProfile() {
console.log('--- 대화형 트랜잭션 예시 ---');
/**
* - Interactive Transaction (대화형 트랜잭션)
* - 내부에서 실행되는 모든 쿼리는 하나의 DB 트랜잭션으로 묶임
* - 내부에서 Error throw 시 → 자동 ROLLBACK
* - 정상 종료 시 → COMMIT
*/
const { user, profile } = await prisma.$transaction(async (tx) => {
/**
* - 이 시점에서 INSERT SQL이 실행됨
* - 실패 시 (unique, not null 등) → 즉시 throw → 트랜잭션 중단
* - 성공 시 → id, email, displayName 반환
*/
const user = await tx.user.create({
data: {
// ❗ 의도적으로 잘못된 도메인 (example.com 아님)
email: 'tx_test@exmaple.com',
displayName: '대화 트랜잭션 사용자',
},
/**
* select는 "반환 객체"만 제한할 뿐
* DB에는 전체 컬럼이 정상적으로 저장됨
*/
select: {
id: true,
email: true,
displayName: true,
},
});
/**
* - DB 오류가 아닌 "도메인 정책 위반" 검사
* - example.com 도메인만 허용
*
* ❗ 여기서 throw 발생 시:
* - 이미 실행된 user.create 포함
* - 트랜잭션 전체가 ROLLBACK 됨
* - User, Profile 모두 DB에 남지 않음
*/
if (!isAllowedEmailDomain(user.email)) {
throw new Error('example.com 도메인 이메일만 가입할 수 있습니다.');
}
/**
* - 위 비즈니스 규칙을 통과한 경우에만 실행
* - user.id 또는 unique key(email) 기반으로 연결
*/
const profile = await tx.profile.create({
data: {
bio: '대화 트랜잭션 사용자',
user: {
connect: {
// unique 필드(email)로 User와 연결
email: 'tx_test@exmaple.com',
},
},
},
});
return { user, profile };
});
console.log('--- 대화형 트랜잭션 예시 종료 ---');
console.log(user);
console.log(profile);
}
비즈니스 규칙 검증 오류로 인한 ROLLBACK 예시

🔷 트랜잭션 형태 선택 기준과 주의 사항
1) 상황에 맞는 트랜잭션 선택 기준
| 구분 | 배열 기반 트랜잭션 (Batch) | 대화형 트랜잭션 (Interactive) |
| 핵심 개념 | “정해진 쿼리를 한 번에 실행” | “로직에 따라 유연하게 실행” |
| 적합한 상황 | 쿼리 순서와 개수가 고정된 단순 작업 | 비즈니스 로직 및 조건 분기가 필요한 복잡한 작업 |
| 로직 제어 | 중간 결과값 이용 불가(if / for 사용 불가) | 중간 결과값에 따른 조건문·반복문 사용 가능 |
| 실무 예시 | • 좋아요 클릭 시 (기록 생성 + 카운트 증가) • 메인 작업 + 단순 히스토리 로그 저장 |
• 회원 가입 (유저 생성 → ID 획득 → 프로필 생성) • 주문 결제 (재고 확인 → 주문 생성 → 포인트 차감) |
| 장점 | 코드가 매우 간결하고 직관적 | 정교하고 유연한 로직 및 에러 핸들링 가능 |
| 단점 | 복잡한 비즈니스 로직 구현 불가 | 코드가 상대적으로 길어질 수 있음 |
| 실무 권장도 | 제한적인 상황에서만 사용 | 실무 표준 (Prisma 7 기준) |
2) 핵심 주의 사항: tx 객체 사용 규칙
대화형 트랜잭션을 사용할 때 가장 많이 하는 실수는 트랜잭션 전용 객체(tx)를 사용하지 않는 것입니다.
이 규칙을 어기면 트랜잭션이 정상적으로 작동하지 않습니다.
❌ 잘못된 코드 (Antipattern)
문제점: prisma 객체를 직접 사용하면 트랜잭션 스코프에 포함되지 않아, 에러가 발생해도 해당 쿼리는 롤백되지 않습니다.
await prisma.$transaction(async (tx) => {
// 전역 prisma 객체를 사용하면 트랜잭션 외부에서 실행됩니다!
await prisma.user.create({ data: { ... } });
});
⭕ 올바른 코드 (Best Practice)
모든 작업이 tx라는 하나의 통로를 통해 실행되어 데이터 정합성이 완벽하게 보장됩니다.
await prisma.$transaction(async (tx) => {
// 반드시 매개변수로 전달된 'tx' 객체를 사용해야 합니다.
await tx.user.create({ data: { ... } });
});
3. 트랜잭션 고급 제어 옵션과 성능 최적화 전략
Prisma 7에서는 Interactive Transactions가 완전히 안정화되었으며, 성능과 가시성(Observability) 측면에서 더욱 정교한 제어가 가능해졌습니다.
await prisma.$transaction(
async (tx) => {
// 로직 수행
},
{
timeout: 5000, // 기본값: 5000ms
maxWait: 2000, // 트랜잭션 시작을 위해 커넥션을 기다리는 최대 시간
isolationLevel: 'ReadCommitted', // DB별 지원 수준 확인 필요
}
)
| 옵션 | 설명 |
| timeout | 트랜잭션 실행 후 완료될 때까지 허용되는 최대 시간 네트워크 지연, 복잡한 JOIN, 대량 INSERT 등으로 인한 장시간 커넥션 점유를 방지하기 위해 반드시 명시적으로 설정하는 것이 바람직합니다. |
| maxWait | 커넥션 풀에서 트랜잭션용 커넥션을 확보하기까지의 최대 대기 시간 커넥션 풀이 고갈된 상황에서 요청을 무한 대기시키지 않고 빠르게 실패(fail-fast) 하여 시스템 전체 마비를 예방합니다. |
| isolationLevel | 트랜잭션 격리 수준을 지정 기본값(ReadCommitted)을 유지하되, 금융·정산·재고 관리처럼 정합성이 중요한 도메인에서만 상위 격리 수준을 신중히 적용합니다. |
🔷 트랜잭션 옵션: timeout과 maxWait
트랜잭션은 DB 커넥션을 점유합니다. 커넥션은 한정된 자원이므로, 이를 효율적으로 관리하는 것이 성능의 핵심입니다.
🔸timeout: 트랜잭션 로직이 실행될 수 있는 총 시간.
🔸maxWait: 커넥션 풀에서 커넥션을 할당받기 위해 기다리는 시간.
✔️ 예제: 사용자 회원가입 시 프로필 동시 생성
async function register(email: string, name: string, bio: string) {
// Prisma Interactive Transaction 시작
// tx 객체는 이 트랜잭션 범위 내에서만 유효
const { user, profile } = await prisma.$transaction(
async (tx) => {
//1. User 테이블에 사용자 생성
const user = await tx.user.create({
data: {
email: email,
displayName: name,
},
});
// >> 의도적으로 지연 발생
// timeout 옵션 테스트를 위한 강제 대기 (10초)
console.log('강제 지연....... 시작...');
await new Promise((res) => setTimeout(res, 10000));
//2. Profile 테이블에 프로필 생성
// 이미 생성된 User를 email 기준으로 연결 (외래키 관계)
const profile = await tx.profile.create({
data: {
bio: bio,
user: {
connect: {
email: email,
},
},
},
});
// 트랜잭션 내부 결과 반환
return { user, profile };
},
{
// 트랜잭션 전체 실행 허용 시간 (초과 시 자동 롤백)
timeout: 5000,
// 커넥션 풀에서 트랜잭션 커넥션을 기다리는 최대 시간
maxWait: 2000,
},
);
// 트랜잭션이 정상 커밋된 경우에만 실행
console.log('--- 사용자 , 프로필 등록 예시 결과 ---');
console.dir(user, { depth: null });
console.dir(profile, { depth: null });
return { user, profile };
}

🔷 격리 수준(Isolation Level)의 실무 적용
동시성 문제가 발생하기 쉬운 좋아요(PostLike) 집계나 게시글 작성자 검증 시나리오에서 격리 수준이 중요합니다.
📌 격리 수준이란?
▸ 격리 수준은 동시에 실행되는 트랜잭션 간 데이터 가시성 규칙을 의미합니다.
▸ Prisma 7 + PostgreSQL 기준에서 주로 사용하는 옵션은 다음과 같습니다.
| Isolation Level | 설명 |
| ReadCommitted | 커밋된 데이터만 읽음 (기본값) |
| RepeatableRead | 트랜잭션 내 동일 쿼리는 항상 동일 결과 |
| Serializable | 가장 강력, 동시성 성능 저하 |
✔️ 예제: 게시글 작성 및 작성자 게시글 수 제한 (RepeatableRead)
특정 사용자가 게시글을 작성하는 동안, 다른 트랜잭션에 의해 작성자의 상태가 변하지 않도록 보장해야 할 때 사용합니다.
async function runRepeatableRead(email: string) {
// 1. 초기 데이터 준비
const user = await prisma.user.create({
data: { email, displayName: 'Test User' },
});
console.log('--- 초기 사용자 생성 완료 ---');
// [Transaction A]: RepeatableRead 격리 수준
const txA = prisma.$transaction(
async (tx) => {
// STEP 1: TX A - 첫 번째 읽기(deletedAt은 null인 상태)
const firstRead = await tx.user.findUnique({ where: { email } });
console.log('[TX A] 1차 읽기 (deletedAt):', firstRead?.deletedAt);
// STEP 2: 의도적인 지연 (3초) (TX B가 업데이트를 수행할 수 있도록 대기)
await new Promise((resolve) => setTimeout(resolve, 3000));
// STEP 4: TX A - 두 번째 읽기 (TX B가 커밋된 후에도 동일한 데이터를 보장하는지 확인)
const secondRead = await tx.user.findUnique({ where: { email } });
console.log('[TX A] 2차 읽기 (deletedAt):', secondRead?.deletedAt);
// 두 읽기 결과가 같으면 격리 수준이 잘 작동하고 있다는 증거입니다.
if (firstRead?.deletedAt === secondRead?.deletedAt) {
console.log('[결과] Repeatable Read 성공: 데이터 일관성 유지');
}
},
{ isolationLevel: 'RepeatableRead' }
);
// [Transaction B]: 외부에서 데이터를 수정(Soft Delete)
const txB = async () => {
await new Promise((resolve) => setTimeout(resolve, 1000)); // TX A 실행 후 시작
// STEP 3: TX B - 데이터 수정 및 즉시 커밋
await prisma.user.update({
where: { email },
data: { deletedAt: new Date() },
});
console.log('[TX B] 데이터 수정 및 커밋 완료');
};
await Promise.all([txA, txB()]);
}

🔷 트랜잭션 최소화: 트랜잭션은 짧고, 단순하고, 명확해야 한다
트랜잭션 내부에 외부 API 호출(이미지 업로드, 알림 발송 등)이 들어가면 커넥션 풀이 순식간에 고갈됩니다.
❌ 나쁜 예시 (Anti-Pattern)
await prisma.$transaction(async (tx) => {
const post = await tx.post.create({ data: { title: 'Hello', authorId: 1 } });
// 외부 API 응답이 늦어지면 DB 커넥션을 계속 붙잡고 있음
await axios.post('https://api.sns.com/share', { id: post.id });
});
✅ 좋은 예시 (Refactored)
// 1. DB에 임시 저장 또는 상태 저장 (트랜잭션 1)
const post = await prisma.post.create({
data: { title: 'Hello', authorId: 1, published: false }
});
// 2. 외부 API 호출 (트랜잭션 외부)
const isShared = await snsService.share(post.id);
// 3. 결과에 따라 상태 업데이트 (트랜잭션 2)
if (isShared) {
await prisma.post.update({
where: { id: post.id },
data: { published: true }
});
}
🔷 Nested Write: $transaction의 강력한 대안
Prisma 스키마에 관계가 정의되어 있다면, $transaction을 명시적으로 쓰지 않고 Nested Write를 쓰는 것이 성능과 가독성 면에서 가장 좋습니다.
Prisma 엔진이 이를 단일 트랜잭션으로 처리합니다.
✔️ 예제: 게시글 + 첫 댓글 + 유저 프로필 수정을 한 번에
async function createPostWithInitialComment(userId: number) {
// 1. 최상위 작업: 'User' 테이블의 특정 레코드를 수정(update)합니다.
const user = await prisma.user.update({
where: { id: userId }, // 수정할 유저의 ID 조건
data: {
// 2. 유저 정보 수정: 해당 유저의 필드를 직접 업데이트합니다.
displayName: 'Active Author',
// 3. 관계형 중첩 생성 (User -> Post):
// 유저 수정과 동시에 해당 유저와 연결된 'Post' 레코드를 생성합니다.
posts: {
create: {
title: 'Prisma 7 Guide',
content: 'Transaction tips',
// 4. 관계형 중첩 생성 (Post -> Comment):
// 게시글 생성과 동시에 해당 게시글에 속한 'Comment' 레코드를 생성합니다.
// depth가 깊어져도 하나의 흐름으로 이어집니다.
comments: {
create: {
content: 'First comment!',
// 작성자 ID를 현재 작업 중인 userId와 연결합니다.
authorId: userId,
},
},
},
},
},
// 5. 결과 반환 설정:
// 생성/수정된 데이터를 즉시 확인하기 위해 관계된 데이터를 모두 포함(fetch)합니다.
include: {
posts: {
include: {
comments: true, // 생성된 댓글까지 깊게(deep) 가져옵니다.
},
},
},
});
return user;
}
4. 트랜잭션 기반 다중 CRUD 처리 실습
✔️ 예제 1: 유저 생성 + 프로필 자동 연결
유저가 가입할 때 프로필 정보를 반드시 함께 생성해야 하는 시나리오입니다.
유저는 생성되었는데 프로필 생성에 실패하면 유저 데이터도 생성되지 않아야 합니다.
async function signupUser(email: string, bio: string) {
// prisma.$transaction을 사용하여 여러 작업을 '하나의 단위'로 묶습니다.
// 이 블록 안의 작업 중 하나라도 실패하면
// 모든 변경사항이 취소(Rollback)되어 데이터 무결성을 보장합니다.
const { user, profile } = await prisma.$transaction(async (tx) => {
// 1. User 테이블에 새로운 레코드 생성
const user = await tx.user.create({
data: {
email,
// 이메일 주소에서 @ 앞부분만 추출하여 초기 닉네임으로 설정
// (예: 'test@naver.com' -> 'test')
displayName: email.split('@')[0],
},
});
// 2. Profile 테이블에 새로운 레코드 생성 및 위에서 생성된 User와 연결
const profile = await tx.profile.create({
data: {
bio,
// 중요: 앞선 단계에서 생성된 user 객체의 id를 외래키(Foreign Key)로 사용합니다.
// 이를 통해 DB 레벨에서 두 데이터 간의 1:1 또는 1:N 관계가 형성됩니다.
userId: user.id,
},
});
// 트랜잭션 성공 시 외부로 반환할 데이터 구성
return { user, profile };
});
// 모든 작업이 성공적으로 완료된 후 콘솔에 결과 출력
console.log("생성된 유저 정보:", user);
console.log("생성된 프로필 정보:", profile);
}
✔️ 예제 2: 게시글 등록 + 좋아요 + 댓글 삽입
운영진이 게시글을 등록함과 동시에 초기 반응(좋아요 1개, 기본 댓글 1개)을 세팅하는 복합 시나리오입니다.
async function seedPostWithEngagement(authorId: number, title: string) {
// 하나라도 실패하면 데이터베이스는 함수 실행 전 상태로 되돌아갑니다(Rollback).
const { post, comment } = await prisma.$transaction(async (tx) => {
// STEP 1: 새로운 게시글 레코드 생성
// tx는 트랜잭션 전용 Prisma Client 인스턴스입니다.
const post = await tx.post.create({
data: {
title,
content: '반갑습니다. 첫 게시글입니다.',
authorId,
published: true, // 생성과 동시에 바로 공개 상태로 설정
},
});
// STEP 2: 게시글 작성 시 본인이 자동으로 '좋아요'를 누르도록 설정
// post.id는 바로 위에서 생성된 게시글의 식별자를 참조합니다.
await tx.postLike.create({
data: {
userId: authorId,
postId: post.id,
},
});
// STEP 3: 시스템 자동 환영 댓글 작성
// 게시글 생성이 완료된 후 해당 게시글에 종속된 댓글을 생성합니다.
const comment = await tx.comment.create({
data: {
content: '게시글 등록을 축하합니다!',
authorId,
postId: post.id,
},
});
// 모든 작업이 성공하면 생성된 데이터를 객체 형태로 반환합니다.
return { post, comment };
});
// 트랜잭션이 성공적으로 완료(Commit)된 후 콘솔에 결과를 출력합니다.
console.log('생성된 게시글:', post);
console.log('생성된 댓글:', comment);
}
✔️ 예제 3: 오류 발생 시 롤백 및 예외 처리
트랜잭션 도중 비즈니스 로직에 의해 에러를 던지거나(throw), Prisma 자체 에러가 발생했을 때 어떻게 안전하게 처리하는지 확인합니다.
async function safeUpdateUserAndPost(postId: number) {
try {
// 1. Prisma 트랜잭션 시작
// tx: 트랜잭션 내에서 사용할 전용 Prisma Client 인스턴스
await prisma.$transaction(
async (tx) => {
// 2. 게시글 정보 조회 (데이터 스냅샷 확보)
// 트랜잭션 내부에서 조회하여 데이터의 일관성을 유지합니다.
const post = await tx.post.findUnique({ where: { id: postId } });
console.log('조회된 게시글:', post);
// 3. [비즈니스 로직 검증] 게시글 상태 체크
// 존재하지 않거나, 이미 삭제된(soft delete) 게시글인 경우 에러를 발생시킵니다.
if (!post || post.deletedAt) {
// 여기서 throw를 던지면 트랜잭션 내의 모든 작업이 취소(Rollback)됩니다.
throw new Error('ALREADY_DELETED_POST');
}
// 4. 게시글 정보 수정
// 조회된 정보를 바탕으로 제목을 수정하고 삭제 일시를 기록합니다.
await tx.post.update({
where: { id: postId },
data: {
title: '[수정됨] ' + post.title,
deletedAt: new Date(),
},
});
// 블록 끝까지 에러 없이 도달하면 모든 변경사항이 DB에 최종 반영(Commit)됩니다.
},
);
} catch (error) {
// A. Prisma 엔진에서 발생하는 구조적 에러 처리
if (error instanceof Prisma.PrismaClientKnownRequestError) {
if (error.code === 'P2025') {
console.error('에러: 업데이트할 레코드를 찾을 수 없습니다 (P2025).');
} else if (error.code === 'P2002') {
console.error('에러: 고유 제약 조건 위반이 발생했습니다 (P2002).');
}
}
// B. 위에서 직접 던진 커스텀 비즈니스 로직 에러 처리
else if (error instanceof Error && error.message === 'ALREADY_DELETED_POST') {
console.warn('알림: 이미 삭제된 게시글이므로 작업을 취소하고 롤백했습니다.');
}
// C. 기타 예상치 못한 런타임 에러
else {
console.error('알 수 없는 에러 발생:', error);
}
// 5. 최종 에러 전파
// 호출한 상위 함수에서 에러 발생 여부를 알 수 있도록 다시 던집니다.
throw error;
}
}
※ 게시된 글 및 이미지 중 일부는 AI 도구의 도움을 받아 생성되거나 다듬어졌습니다.
'4.Node.js > Prisma(ORM)' 카테고리의 다른 글
| [Prisma7] 9편. Node.js + Prisma 에러 종류 및 처리 방법 (0) | 2026.02.02 |
|---|---|
| [Prisma7] 8편. Prisma 데이터베이스 동기화(Migration & Sync)실습 : 개발 → 운영 (0) | 2026.01.22 |
| [Prisma7] 6편. Prisma로 해결되지 않는 쿼리 다루기: Raw SQL 실전 활용 (0) | 2026.01.20 |
| [Prisma7] 5편. Prisma 관계 조회 심화: include / select와 중첩 관계 탐색 (0) | 2026.01.19 |
| [Prisma7] 4편. 조회 쿼리 고급 옵션(where)과 관계 데이터 생성(create) 패턴 이해 (0) | 2026.01.16 |