Автор оригинала: Vlad Mihalcea.
Вступление
В этой статье мы рассмотрим, как работает свойство оптимистичной версии блокировки при использовании JPA и Hibernate.
Чаще всего мы упускаем из виду базовые концепции и сосредотачиваемся только на более сложных темах, таких как ассоциации или запросы, не понимая, что базовые сопоставления также могут оказать существенное влияние, когда речь заходит об эффективности и эффективности сохранения.
Свойство оптимистичной версии блокировки с JPA и гибернацией @vlad_mihalcea https://t.co/mcESSRxB48 pic.twitter.com/oOHIgkhZDl
Аномалия потерянных обновлений
Системы реляционных баз данных появились во времена систем мэйнфреймов, когда клиент взаимодействовал с базой данных с терминала, используя выделенное соединение. Тогда, когда клиенту нужно было работать с базой данных, был открыт сеанс, и все операции чтения и записи выполнялись в рамках одной и той же транзакции базы данных.
Таким образом, механизм управления параллелизмом может гарантировать, что операции чтения и записи, поступающие от нескольких одновременных клиентов, могут быть правильно чередованы, чтобы они не нарушали сериализуемость.
Однако это взаимодействие с базой данных на основе сеанса больше не является нормой, особенно при доступе к службам через Интернет. Это связано с тем, что вы больше не можете удерживать соединение с базой данных по нескольким HTTP-запросам. По этой причине гарантии ACID больше не действуют, когда транзакция на уровне приложения охватывает несколько HTTP-запросов, а также физические транзакции базы данных.
Одним из примеров аномалии, которая может возникнуть из-за того, что операции чтения и записи выполняются в разных транзакциях базы данных, является явление потерянного обновления.
Чтобы понять аномалию потерянного обновления, рассмотрим следующий пример:
Поток действий происходит следующим образом:
- Алиса загружает
Продукт
в количестве 5 штук. - Сразу после этого пакетный процесс склада обновляет количество
Продукта
до0
. - Алиса решает купить
Товар
, поэтому, когда количество уменьшается, мы получаем отрицательное значение количества.
Потерянное обновление происходит потому, что Алиса думает, что все еще есть доступные продукты, в то время как на самом деле для покупки продукта не осталось.
Свойство оптимистической версии блокировки
Для решения этой проблемы можно использовать столбец @Версия
:
@Entity(name = "Product") @Table(name = "product") public class Product { @Id private Long id; private int quantity; @Version private int version; //Getters and setters omitted for brevity }
Теперь, когда Продукт
объект извлечен:
Product product = entityManager.find( Product.class, 1L );
Свойству version
присваивается значение, найденное в связанной записи product
таблицы в момент загрузки сущности.
Теперь, когда Продукт
объект изменен:
product.setQuantity(0);
Hibernate собирается включить столбец версия
в критерии фильтрации строк:
UPDATE product SET quantity = 0, version = 2 WHERE id = 1 AND version = 1
Обратите внимание, что в столбце версия
установлено следующее значение, в то время как предыдущее значение, считанное при извлечении сущности из базы данных, используется для фильтрации записи.
Таким образом, если значение столбца версия
изменилось, обновление не произойдет и значение 0
будет возвращен методом executeUpdate
JDBC PreparedStatement
.
Когда режим гибернации считывает значение количества обновлений, равное 0
, и javax.настойчивость.Будет выдано исключение OptimisticLockException
.
Свойство version
также используется для инструкции DELETE SQL, поэтому, если мы удалим сущность Product
:
Product product = entityManager.getReference( Product.class, 1L ); entityManager.remove(product);
Hibernate выполняет следующую инструкцию DELETE:
DELETE FROM product WHERE id = 1 AND version = 1
Обратите внимание, что столбец версия
используется в предложении WHERE, чтобы убедиться, что мы удаляем тот же снимок сущности, который мы ранее считывали из базы данных.
Теперь, возвращаясь к нашему предыдущему примеру, если мы сейчас используем столбец версия
, мы можем предотвратить потерю обновления, как показано на следующей диаграмме:
На этот раз поток действий происходит следующим образом:
- Алиса загружает
Продукт
в количестве 5 штук. - Сразу после этого пакетный процесс склада обновляет количество
Продукта
до0
. - Алиса решает купить
Продукт
, поэтому она уменьшает количество и пытается выполнить ОБНОВЛЕНИЕ. - ОБНОВЛЕНИЕ предотвращено, поскольку продукт
версия
больше не1
, и Hibernate выдает исключениеOptimisticLockException
.
Круто, правда?
Вывод
Знание того, как работает свойство оптимистичной версии блокировки, очень важно при использовании JPA и Hibernate, поскольку оно позволяет предотвратить аномалию потерянного обновления, когда данный объект изменяется несколькими одновременными пользователями.
Для получения более подробной информации о феномене потерянного обновления и о том, как база данных предотвращает его в рамках одной транзакции базы данных, ознакомьтесь со следующей статьей .