프리즈마의 공식 홈페이지를 가면 트랜잭션 방법엔 2가지가 있습니다.
const createOne = prisma.team.create({
data: {
name: 'Aurora Adventures',
members: {
create: {
email: 'alice@prisma.io',
},
},
},
})
// Nested write
const createTwo = prisma.team.create({
data: {
name: 'Cool Crew',
members: {
create: {
email: 'elsa@prisma.io',
},
},
},
})
// $transaction API
await prisma.$transaction([createTwo, createOne])
generator client {
provider = "prisma-client-js"
previewFeatures = ["interactiveTransactions"] <- 추가 필요
}
schema.prisma
에 위 설정을 등록해줍니다. (4.6버전 이상부터는 필요 X)
async function transfer(from: string, to: string, amount: number) {
return await prisma.$transaction(async (prisma) => {
// 1. Decrement amount from the sender.
const sender = await prisma.account.update({
data: {
balance: {
decrement: amount,
},
},
where: {
email: from,
},
})
// 2. Verify that the sender's balance didn't go below zero.
if (sender.balance < 0) {
throw new Error(`${from} doesn't have enough to send ${amount}`)
}
// 3. Increment the recipient's balance by amount
const recipient = await prisma.account.update({
data: {
balance: {
increment: amount,
},
},
where: {
email: to,
},
})
return recipient
})
}
async function main() {
// This transfer is successful
await transfer('alice@prisma.io', 'bob@prisma.io', 100)
// This transfer fails because Alice doesn't have enough funds in her account
await transfer('alice@prisma.io', 'bob@prisma.io', 100)
}
isolation level도 설정해줄 수 있지만 기본적으로 제가 사용하는 mysql은 repeatableRead기 때문에 따로 작업을 안해주겠습니다.
@Injectable()
export class BoardService {
constructor(
private readonly boardsRepository: BoardRepository,
private readonly categoriesRepository: CategoryRepository,
private readonly prisma: PrismaClient,
) {}
async create(userId: number, dto: CreateBoardDto): Promise<board> {
const [boards] = await this.prisma.$transaction([
this.boardsRepository.create(userId, dto),
this.categoriesRepository.multiCreate(dto.categories),
]);
return boards;
}
}
저는 repository형식으로 했기 때문에 repository를 가져와서 실행하였고 insert였기 때문에 순차 방법으로 사용했습니다.
하지만 여기서 문제점이 발생했는데요
n-n 관계 테이블에도 데이터를 삽입해줘야하는데 트랜잭션이 끝난 후 넣어주면 데이터에 대해 보장을 해줄 수가 없는 문제점이 있었습니다.
그래서 2번째 방법으로 방향을 틀었는데요 이것도 문제가 있었습니다..😅
priview 방법에서는 무조건 transaction안에 있는 인자로 prisma에 접근해야 트랜잭션이 실행되는 문제였습니다.
return await this.prisma.$transaction(async (prisma) => {
const board = await prisma.board.create({
data: {
title: dto.title,
context: dto.context,
thumbnail: dto.thumbnail !== null ? dto.thumbnail : 'thumbnail',
user: {
connect: {
id: userId,
},
},
},
});
dto.categories.map(async (name) => {
const category = await prisma.category.upsert({
where: {
name: category,
},
update: {},
create: {
name: category,
},
});
await prisma.categories_on_boards.create({
data: {
categoryId: category.id,
boardId: board.id,
},
});
});
return board;
});
위 코드처럼 트랜잭션 안의 인자로 구성되어야하기 때문에 repository에서 구현하는 저로서는 사용하기 꺼려지는 방법이였습니다.
하지만 n-n 관계 테이블에도 데이터를 삽입해줘야한다면.. 어쩔 수 없이 사용해야되는 방법으로 볼 수도 있을 것 같습니다ㅠㅠㅠ
더 좋은 방법이 있다면 댓글로 남겨주세요 제발 🙏
항상 사용하던데로 await을 쓰게 되면 아래 에러를 맛볼 수 있습니다..ㅎㅎ
아래 에러가 나는 이유는 트랜잭션의 결과값이 PrismaPromise로 나와야되기 때문입니다.
그래서 선언부나 호출부에서 async와 await을 안쓰는게 포인트라고 볼 수 있습니다
도움이 된 링크들
https://stackoverflow.com/questions/70893348/rollback-of-prisma-interactive-transaction-in-nestjs-not-working-when-throwing-a
https://www.prisma.io/docs/concepts/components/prisma-client/transactions#interactive-transactions
https://github.com/prisma/prisma/issues/1844
https://stackoverflow.com/questions/70305255/prisma-transaction-in-nodejs-and-typescript-not-working