cheatsheets

Транзакции

Последовательность операций чтения/записи, которые обрабатываются как единое целое, либо все операции завершаются успешно, либо все операции отклоняются с ошибкой.

Три способа транзакции

const newUserWithProfile = await prisma.user.create({
    data: {
        email,
        profile: {
            // !
            create: {
                first_name,
                last_name
            }
        }
    }
})
const removedUser = await prisma.user.delete({
    where: {
        email
    }
})

// !
await prisma.post.deleteMany({
    where: {
        author_id: removedUser.id
    }
})

$transaction

Интерфейс $transaction может быть использован в двух формах:

Пример транзакции, возвращающей посты, в заголовке которых встречается слово TypeScript и общее количество постов:

const [postsAboutTypeScript, totalPostCount] = await prisma.$transaction([
    prisma.post.findMany({
        where: {
            title: {
                contains: 'TypeScript'
            }
        }
    }),
    prisma.post.count()
])

В $transaction допускается использование SQL:

const [userNames, updatedUser] = await prisma.$transaction([
    prisma.$queryRaw`SELECT 'user_name' FROM users`,
    prisma.$executeRaw`UPDATE users SET user_name = 'Harry' WHERE id = 42`
])

Интерактивные транзакции

Предоставляют разработчикам больший контроль над выполняемыми операциями в контексте транзакции.

В данный момент они имеют статус экспериментальной возможности, которую можно включить следующим образом:

generator client {
  provider        = "prisma-client-js"
  previewFeatures = ["interactiveTransactions"]
}

Рассмотрим пример совершения платежа: Предположим, что у Alice и Bob имеется по 100$ на счетах (account), и Alice хочет отправить Bob свои 100$.

import {PrismaClient} from '@prisma/client'

const prisma = new PrismaClient()

async function transfer(from, to, amount) {
    try {
        await prisma.$transaction(async (prisma) => {
            // 1. Уменьшаем баланс отправителя
            const sender = await prisma.account.update({
                data: {
                    balance: {
                        decrement: amount
                    }
                },
                where: {
                    email: from
                }
            })

            // 2. Проверяем, что баланс отправителя после уменьшения >= 0
            if (sender.balance < 0) {
                throw new Error(`${from} имеет недостаточно средств для отправки ${amount}`)
            }

            // 3. Увеличиваем баланс получателя
            const recipient = await prisma.account.update({
                data: {
                    balance: {
                        increment: amount
                    }
                },
                where: {
                    email: to
                }
            })

            return recipient
        })
    } catch (e) {
        // обрабатываем ошибку
    }
}

async function main() {
    // эта транзакция разрешится
    await transfer('alice@mail.com', 'bob@mail.com', 100)
    // а эта провалится
    await transfer('alice@mail.com', 'bob@mail.com', 100)
}

main().finally(() => {
    prisma.$disconnect()
})