Объектно-реляционное отображение — Примеры

Пример 1 — Обход, который превращается в запрос на строку

На экране операций перечислены все открытые заказы, а их клиентам уходит напоминание. На вид в коде две инструкции, однако вторая выполняется по разу на заказ, и чем длиннее список, тем медленнее экран. Уберите повторяющуюся инструкцию и покажите доказательство, что она исчезла.

orders = session.all("select * from orders where status = 'open'")

for order in orders:
    send_reminder(order.customer.email)

Решение

orders = session.all(
    "select o.id, c.email from orders o"
    " join customers c on c.id = o.customer_id"
    " where o.status = 'open'"
)

for order in orders:
    send_reminder(order.email)

# журнал инструкций
# select o.id, c.email from orders o join customers c ... -> 1 инструкция, 214 строк

Связь загружается вместе с исходным запросом, поэтому число инструкций больше не зависит от размера списка, а доказывает это строка журнала: сам код выглядел бы почти одинаково в обоих случаях. Включать журналирование инструкций при разработке — привычка, которая находит такие дефекты до того, как их увидит боевая среда. Обычно ошибка в другом: переносят цикл, а не загрузку, — обход прячется во вспомогательной функции или сериализаторе, и лишний запрос всплывает там, где код экрана о нём и не упоминает.

Пример 2 — Перенос записи внутрь её транзакции

Оформление заказа добавляет строку заказа, резервирует товар и делает запись в реестре. Удаться или провалиться эти три шага должны вместе, но сброс отображателя происходит раньше двух из них, а фиксация — после возврата из функции. Поставьте границу туда, где код её заявляет.

def place_order(cart):
    session.add(Order(cart.id))
    session.flush()              # вставка выдаётся здесь

    stock.reserve(cart.items)
    ledger.record(cart.total)

# вызывающий фиксирует гораздо позже, после отправки ответа

Решение

def place_order(cart):
    with session.transaction():  # сброс ниже происходит внутри этого блока
        session.add(Order(cart.id))
        session.flush()          # вставка выдана, но ещё не зафиксирована

        stock.reserve(cart.items)
        ledger.record(cart.total)
    # фиксация при выходе из блока; сбой выше не сохранит ни одну из трёх

# сессия завершается вместе с запросом, поэтому карта идентичности её не переживает

Явный блок не даёт зафиксировать резерв и запись в реестре без заказа, а сбой до конца блока не оставляет частичного состояния. Ограничение сессии одним запросом важно по той же причине. Живущая долго сессия переиспользует устаревшие объекты и растёт без предела. Чаще всего транзакцию ставят вокруг кода, который уже записал данные, — из предположения, что сброс совпадает с вызвавшей его строкой.