Это основано на презентации внутреннего тренинга, проведенной в CompareEuropeGroup, адаптированной к этому формату.

Транзакции базы данных

Транзакции - фундаментальная концепция всех систем баз данных. Существенным моментом транзакции является то, что она объединяет несколько шагов в одну операцию по принципу все или ничего. Промежуточные состояния между шагами не видны другим параллельным транзакциям, и если произойдет какой-либо сбой, препятствующий завершению транзакции, то ни один из шагов не повлияет на базу данных вообще.

из PostgreSQL Docs

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

Представьте себе следующий пример:

Это перевод 100 между двумя учетными записями (Боб и Алиса). Представьте, что существует правило, согласно которому ни одна учетная запись не может иметь отрицательный баланс, а на счету Боба меньше 100. Вторая команда потерпит неудачу.

Если бы эти операции не были выполнены в рамках транзакции, мы бы испортили данные - у Алисы были бы лишние 100, пришедшие из ниоткуда.

В рамках транзакции, как показано выше, когда вторая команда завершается ошибкой, выполняется откат первой . Алиса никогда не получает лишних 100, и операция помечается как неудачная. Мы обеспечиваем непротиворечивость данных - неудачная операция не повлекла за собой никаких последствий.

Гибернация

Hibernate - один из наиболее часто используемых ORM, и его можно использовать в приложениях Grails. Как и все ORM, он служит уровнем абстракции поверх базы данных, чтобы избежать выполнения SQL-запросов вручную. Он реализует стандарт JDBC для доступа к базе данных и соблюдает стандарт JPA.

Он имеет множество вариантов поведения по умолчанию для повышения производительности запросов к базе данных (ленивая инициализация, оптимистическая блокировка) и имеет внутренний кеш, управляемый сеансом, чтобы уменьшить количество запросов к базе данных.

  • Кэш первого уровня - уникален за сеанс. Сохраняет результат выполненных запросов, чтобы уменьшить количество обращений к базе данных
  • Кэш второго уровня - настраивается по классам, позволяет совместно использовать кэшированные объекты между различными сеансами.

Сброс

Flush - это команда Hibernate, которая заставляет кэшируемые запросы запускаться немедленно, таким образом синхронизируя соединение с объектами в памяти.

Спящий режим и транзакции

Как и ожидалось, спящий режим поддерживает использование транзакций. Рассмотрим следующий пример:

Этот код является Java-эквивалентом предыдущих SQL-запросов. Если во время изменения баланса произойдет какое-либо исключение, все изменения будут отменены, и никакие данные не будут повреждены.

Транзакции Grails

В зависимости от используемой вами версии Grails поведение транзакций по умолчанию будет меняться. В версиях 3.0.x все службы по умолчанию являются транзакционными, а в версиях 3.1.x и выше транзакционные службы должны быть указаны как таковые.

Обратите внимание, что для использования доступны 2 транзакционные аннотации:

  • grails.transaction.Transactional - транзакция Grails. Это, в отличие от Spring, является преобразованием AST и устраняет необходимость в прокси.
  • org.springframework.transaction.annotation.Transactional - Транзакция Spring по умолчанию, с использованием прокси, когда интерфейс не задействован. Подробнее читайте в их доках.

Эта аннотация более или менее представит код транзакции Hibernate, который мы видели ранее в транзакционных методах.

Что делает промывка?

Вызов domainObject.save (flush: true) не приведет к выполнению команды фиксации внутри транзакции. Это не нарушает нормальный поток транзакций.

Транзакции и многопоточность

При одновременной работе с несколькими потоками и запросами (или транзакциями) к базе данных могут возникнуть определенные проблемы. Чтобы этого не произошло, нужно помнить о некоторых вещах:

  1. У каждого потока будет свой собственный сеанс гибернации, что означает, что кеш не будет использоваться совместно и может очень быстро устареть.
    Это связано с тем, что Hibernate может не сразу фиксироваться в базе данных (по умолчанию он управляет ею изнутри, хотя в конце каждого сеанса / потока он все сбрасывает). Этим можно управлять с помощью транзакций сброса и / или меньшего размера.
    Кроме того, из-за кеширования при чтении данных они могут не отражать последние изменения в базе данных. В Grails есть метод refresh (), который принудительно обновляет внутренний кеш.
  2. Если оптимистическая блокировка не включена, возможно, возникнут тупиковые ситуации. По общему признанию, это произойдет в основном из-за плохо управляемых транзакций. Представьте следующий сценарий:

У каждого из этих двух методов есть шанс заблокировать другой, даже в разных потоках, потому что они могут вызвать блокировку базы данных. Поскольку долго работающая служба может работать долгое время, вызовы других методов с тем же идентификатором будут зависать до ее завершения. Если это очень часто используемый объект, это быстро вызовет проблемы.

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

Транзакции - это полезная парадигма, которая помогает нам поддерживать согласованность данных даже при выполнении нескольких сложных операций. У них есть некоторые накладные расходы, поскольку управление транзакциями - сложная задача. Чтобы помочь в управлении транзакциями, ORM, такие как Hibernate, интегрированные с такими фреймворками, как Spring или Grails, предоставляют несколько механизмов для создания транзакций.

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