Рубрики
Без рубрики

Оптимистичная блокировка с помощью JPA и гибернации

Узнайте, как работает свойство оптимистичной блокировки версии при использовании JPA и гибернации и как оно может помочь предотвратить аномалию потерянного обновления.

Автор оригинала: Vlad Mihalcea.

Вступление

В этой статье мы рассмотрим, как работает свойство оптимистичной версии блокировки при использовании JPA и Hibernate.

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

Свойство оптимистичной версии блокировки с JPA и гибернацией @vlad_mihalcea https://t.co/mcESSRxB48 pic.twitter.com/oOHIgkhZDl

Аномалия потерянных обновлений

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

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

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

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

Чтобы понять аномалию потерянного обновления, рассмотрим следующий пример:

Поток действий происходит следующим образом:

  1. Алиса загружает Продукт в количестве 5 штук.
  2. Сразу после этого пакетный процесс склада обновляет количество Продукта до 0 .
  3. Алиса решает купить Товар , поэтому, когда количество уменьшается, мы получаем отрицательное значение количества.

Потерянное обновление происходит потому, что Алиса думает, что все еще есть доступные продукты, в то время как на самом деле для покупки продукта не осталось.

Свойство оптимистической версии блокировки

Для решения этой проблемы можно использовать столбец @Версия :

@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, чтобы убедиться, что мы удаляем тот же снимок сущности, который мы ранее считывали из базы данных.

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

На этот раз поток действий происходит следующим образом:

  1. Алиса загружает Продукт в количестве 5 штук.
  2. Сразу после этого пакетный процесс склада обновляет количество Продукта до 0 .
  3. Алиса решает купить Продукт , поэтому она уменьшает количество и пытается выполнить ОБНОВЛЕНИЕ.
  4. ОБНОВЛЕНИЕ предотвращено, поскольку продукт версия больше не 1 , и Hibernate выдает исключение OptimisticLockException .

Круто, правда?

Вывод

Знание того, как работает свойство оптимистичной версии блокировки, очень важно при использовании JPA и Hibernate, поскольку оно позволяет предотвратить аномалию потерянного обновления, когда данный объект изменяется несколькими одновременными пользователями.

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