Prisma ORM: полное руководство для начинающих (и не только). Часть 2

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.



Привет, друзья!


В этой серии из 2 статей я хочу поделиться с вами своими заметками о Prisma.


Prisma — это современное (продвинутое) объектно-реляционное отображение (Object-Relational Mapping, ORM) для Node.js и TypeScript. Проще говоря, Prisma — это инструмент, позволяющий работать с реляционными (PostgreSQL, MySQL, SQL Server, SQLite) и нереляционной (MongoDB) базами данных с помощью JavaScript или TypeScript без использования SQL (хотя такая возможность имеется).


Содержание этой части


  • Настройки
    • select
    • include
    • where
    • orderBy
    • distinct
  • Вложенные запросы
  • Фильтры и операторы
    • Фильтры
    • Операторы
  • Фильтры для связанных записей
  • Методы клиента
  • Транзакции
    • $transaction
    • Интерактивные транзакции

Первая часть.


Если вам это интересно, прошу под кат.


Клиент


Настройки


select


select определяет, какие поля включаются в возвращаемый объект.


const user = await prisma.user.findUnique({
  where: { email },
  select: {
    id: true,
    email: true,
    first_name: true,
    last_name: true,
    age: true
  }
})

// or
const usersWithPosts = await prisma.user.findMany({
  select: {
    id: true,
    email: true,
    posts: {
      select: {
        id: true,
        title: true,
        content: true,
        author_id: true,
        created_at: true
      }
    }
  }
})

// or
const usersWithPostsAndComments = await prisma.user.findMany({
  select: {
    id: true,
    email: true,
    posts: {
      include: {
        comments: true
      }
    }
  }
})

include


include определяет, какие отношения (связанные записи) включаются в возвращаемый объект.


const userWithPostsAndComments = await prisma.user.findUnique({
  where: { email },
  include: {
    posts: true,
    comments: true
  }
})

where


where определяет один или более фильтр (о фильтрах мы поговорим отдельно), применяемый к свойствам записи или связанных записей:


const admins = await prisma.user.findMany({
  where: {
    email: {
      contains: 'admin'
    }
  }
})

orderBy


orderBy определяет поля и порядок сортировки. Возможными значениями orderBy являются asc и desc.


const usersByPostCount = await prisma.user.findMany({
  orderBy: {
    posts: {
      count: 'desc'
    }
  }
})

distinct


distinct определяет поля, которые должны быть уникальными в возвращаемом объекте.


const distinctCities = await prisma.user.findMany({
  select: {
    city: true,
    country: true
  },
  distinct: ['city']
})

Вложенные запросы


  • create: { data } | [{ data1 }, { data2 }, ...{ dataN }] — добавляет новую связанную запись или набор записей в родительскую запись. create доступен при создании (create) новой родительской записи или обновлении (update) существующей родительской записи

const user = await prisma.user.create({
  data: {
    email,
    profile: {
      // вложенный запрос
      create: {
        first_name,
        last_name
      }
    }
  }
})

  • createMany: [{ data1 }, { data2 }, ...{ dataN }] — добавляет набор новых связанных записей в родительскую запись. createMany доступен при создании (create) новой родительской записи или обновлении (update) существующей родительской записи

const userWithPosts = await prisma.user.create({
  data: {
    email,
    posts: {
      // !
      createMany: {
        data: posts
      }
    }
  }
})

  • update: { data } | [{ data1 }, { data2 }, ...{ dataN }] — обновляет одну или более связанных записей

const user = await prisma.user.update({
  where: { email },
  data: {
    profile: {
      // !
      update: { age }
    }
  }
})

  • updateMany: { data } | [{ data1 }, { data2 }, ...{ dataN }] — обновляет массив связанных записей. Поддерживается фильтрация

const result = await prisma.user.update({
  where: { id },
  data: {
    posts: {
      // !
      updateMany: {
        where: {
          published: false
        },
        data: {
          like_count: 0
        }
      }
    }
  }
})

  • upsert: { data } | [{ data1 }, { data2 }, ...{ dataN }] — обновляет существующую связанную запись или создает новую

const user = await prisma.user.update({
  where: { email },
  data: {
    profile: {
      // !
      upsert: {
        create: { age },
        update: { age }
      }
    }
  }
})

  • delete: boolean | { data } | [{ data1 }, { data2 }, ...{ dataN }] — удаляет связанную запись. Родительская запись при этом не удаляется

const user = await prisma.user.update({
  where: { email },
  data: {
    profile: {
      delete: true
    }
  }
})

  • deleteMany: { data } | [{ data1 }, { data2 }, ...{ dataN }] — удаляет связанные записи. Поддерживается фильтрация

const user = await prisma.user.update({
  where: { id },
  data: {
    age,
    posts: {
      // !
      deleteMany: {}
    }
  }
})

  • set: { data } | [{ data1 }, { data2 }, ...{ dataN }] — перезаписывает значение связанной записи

const userWithPosts = await prisma.user.update({
  where: { email },
  data: {
    posts: {
      // !
      set: newPosts
    }
  }
})

  • connect — подключает запись к существующей связанной записи по идентификатору или уникальному полю

const user = await prisma.post.create({
  data: {
    title,
    content,
    author: {
      connect: { email }
    }
  }
})

  • connectOrCreate — подключает запись к существующей связанной записи по идентификатору или уникальному полю либо создает связанную запись при отсутствии таковой;
  • disconnect — отключает родительскую запись от связанной без удаления последней. disconnect доступен только если отношение является опциональным.

Фильтры и операторы


Фильтры


  • equals — значение равняется n

const usersWithNameHarry = await prisma.user.findMany({
  where: {
    name: {
      equals: 'Harry'
    }
  }
})

// `equals` может быть опущено
const usersWithNameHarry = await prisma.user.findMany({
  where: {
    name: 'Harry'
  }
})

  • not — значение не равняется n;
  • in — значение n содержится в списке (массиве)

const usersWithNameAliceOrBob = await prisma.user.findMany({
  where: {
    user_name: {
      // !
      in: ['Alice', 'Bob']
    }
  }
})

  • notInn не содержится в списке;
  • ltn меньше x

const notPopularPosts = await prisma.post.findMany({
  where: {
    likeCount: {
      lt: 100
    }
  }
})

  • lten меньше или равно x;
  • gtn больше x;
  • gten больше или равно x;
  • containsn содержит x

const admins = await prisma.user.findMany({
  where: {
    email: {
      contains: 'admin'
    }
  }
})

  • startsWithn начинается с x

const usersWithNameStartsWithA = await prisma.user.findMany({
  where: {
    user_name: {
      startsWith: 'A'
    }
  }
})

  • endsWithn заканчивается x.

Операторы


  • AND — все условия должны возвращать true

const notPublishedPostsAboutTypeScript = await prisma.post.findMany({
  where: {
    AND: [
      {
        title: {
          contains: 'TypeScript'
        }
      },
      {
        published: false
      }
    ]
  }
})

Обратите внимание: оператор указывается до названия поля (снаружи поля), а фильтр после (внутри).


  • OR — хотя бы одно условие должно возвращать true;
  • NOT — все условия должны возвращать false.

Фильтры для связанных записей


  • some — возвращает все связанные записи, соответствующие одному или более критерию фильтрации

const usersWithPostsAboutTypeScript = await prisma.user.findMany({
  where: {
    posts: {
      some: {
        title: {
          contains: 'TypeScript'
        }
      }
    }
  }
})

  • every — возвращает все связанные записи, соответствующие всем критериям;
  • none — возвращает все связанные записи, не соответствующие ни одному критерию;
  • is — возвращает все связанные записи, соответствующие критерию;
  • notIs — возвращает все связанные записи, не соответствующие критерию.

Методы клиента


  • $disconnect — закрывает соединение с БД, которое было установлено после вызова метода $connect (данный метод чаще всего не требуется вызывать явно), и останавливает движок запросов (query engine) Prisma

import { PrismaClient } from '@prisma/client'

const prisma = new PrismaClient()

async function seedDb() {
  try {
    await prisma.model.create(data)
  } catch (e) {
    onError(e)
  } finally {
    // !
    await prisma.$disconnect()
  }
}

  • $use — добавляет посредника (middleware)

prisma.$use(async (params, next) => {
  console.log('Это посредник')

  // работаем с `params`

  return next(params)
})

  • next — представляет "следующий уровень" в стеке посредников. Таким уровнем может быть следующий посредник или движок запросов Prisma;
  • params — объект со следующими свойствами:
    • action — тип запроса, например, create или findMany;
    • args — аргументы, переданные в запрос, например, where или data;
    • model — модель, например, User или Post;
    • runInTransaction — возвращает true, если запрос был запущен в контексте транзакции;

  • методы $queryRaw, $executeRaw и $runCommandRaw предназначены для работы с SQL. Почитать о них можно здесь;
  • $transaction — выполняет запросы в контексте транзакции (см. ниже).

Подробнее о клиенте можно почитать здесь.


Транзакции


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


Prisma позволяет использовать транзакции тремя способами:


  • вложенные запросы (см. выше): операции с родительскими и связанными записями выполняются в контексте одной транзакции

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

  • пакетированные/массовые (batch/bulk) транзакции: выполнение нескольких операций за один раз с помощью таких запросов, как createMany, updateMany и deleteMany

const removedUser = await prisma.user.delete({
  where: {
    email
  }
})

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

  • вызов метода $transaction.

$transaction


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


  • $transaction([ query1, query2, ...queryN ]) — принимает массив последовательно выполняемых запросов;
  • $transaction(fn) — принимает функцию, которая может включать запросы и другой код.

Пример транзакции, возвращающей посты, в заголовке которых встречается слово 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()
})

Подробнее о транзакциях можно почитать здесь.


Пожалуй, это все, что я хотел рассказать вам о Prisma.


Благодарю за внимание и happy coding!




Источник: https://habr.com/ru/company/timeweb/blog/654567/


Интересные статьи

Интересные статьи

В процессе разработки приложения, поскольку у нас не было четкого технического задания и видения, у нас возник некий технический долг в виде any, в этой статье мы постараемся это исправить. И в этом н...
Текст написан иностранным агентом – лицом, проживающим за пределами России (в Канаде). Иллюстрации взяты из открытых источников - если не указано иное, из Википедии.В предыдущей статье речь шла о том,...
В этой части статьи про торгового бота мы сначала посмотрим на алгоритм сравнения двух комбинаций, а потом на разные методы сбора комбинаций в кластеры. Разберем как работает сам метод сбора кластеров...
ВведениеВ данной статье я бы хотел рассмотреть проблему обновления PHP в виртуальной машине BitrixVM, и действия, которые возможно применить если выполнение переезда на машину с обновленным ПО невозмо...
Данный перевод является второй частью перевода протокола безопасности транспортного уровня (TLS) версии 1.2 (RFC 5246). Первая часть перевода находится  здесь. Вторая часть перевода содержит опис...