«1. Введение

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

Более того, нам нужно убедиться, что данные остаются согласованными между одновременным чтением и обновлением.

Для этого мы можем использовать оптимистичный механизм блокировки, предоставляемый Java Persistence API. Это приводит к тому, что несколько одновременных обновлений одних и тех же данных не мешают друг другу.

2. Понимание оптимистической блокировки

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

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

Если за это время значение изменилось, создается исключение OptimisticLockException. В противном случае транзакция фиксирует обновление и увеличивает значение свойства версии.

3. Пессимистическая блокировка против оптимистичной блокировки

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

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

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

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

Напротив, пессимистический механизм блокировки предполагает блокировку сущностей на уровне базы данных.

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

4. Атрибуты версии

Атрибуты версии — это свойства с аннотацией @Version. Они необходимы для включения оптимистической блокировки. Давайте посмотрим на пример класса сущностей:

@Entity
public class Student {

    @Id
    private Long id;

    private String name;

    private String lastName;

    @Version
    private Integer version;

    // getters and setters

}

Есть несколько правил, которым мы должны следовать при объявлении атрибутов версии:

    каждый класс сущностей должен иметь только один атрибут версии, он должен быть помещен в первичную таблицу для отображаемой сущности. для нескольких таблиц тип атрибута версии должен быть одним из следующих: int, Integer, long, Long, short, Short, java.sql.Timestamp

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

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

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

5. Режимы блокировки

JPA предоставляет нам два разных оптимистичных режима блокировки (и два псевдонима):

    OPTIMISTIC — он получает оптимистическую блокировку чтения для всех объектов, содержащих атрибут версии OPTIMISTIC_FORCE_INCREMENT — это получает оптимистическую блокировку, такую ​​же, как OPTIMISTIC, и дополнительно увеличивает значение атрибута версии READ — это синоним OPTIMISTIC WRITE — это синоним OPTIMISTIC_FORCE_INCREMENT

Мы можем найти все перечисленные выше типы в классе LockModeType.

5.1. ОПТИМИСТИЧЕСКИЙ (ЧИТАТЬ)

«Как мы уже знаем, режимы блокировки OPTIMISTIC и READ являются синонимами. Однако спецификация JPA рекомендует использовать OPTIMISTIC в новых приложениях.

Всякий раз, когда мы запрашиваем режим блокировки OPTIMISTIC, поставщик постоянства предотвратит грязное чтение наших данных, а также неповторяющееся чтение.

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

    обновила или удалила, но не зафиксировала, успешно обновила или удалила за это время

5.2. OPTIMISTIC_INCREMENT (WRITE)

Как и раньше, OPTIMISTIC_INCREMENT и WRITE являются синонимами, но первое предпочтительнее.

OPTIMISTIC_INCREMENT должен соответствовать тем же условиям, что и режим блокировки OPTIMISTIC. Кроме того, он увеличивает значение атрибута версии. Однако не указано, следует ли это сделать немедленно или можно отложить до фиксации или сброса.

Стоит знать, что поставщику постоянства разрешено предоставлять функциональность OPTIMISTIC_INCREMENT, когда запрашивается режим блокировки OPTIMISTIC.

6. Использование оптимистической блокировки

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

6.1. Find

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

entityManager.find(Student.class, studentId, LockModeType.OPTIMISTIC);

6.2. Query

Другой способ включить блокировку — использовать метод setLockMode объекта Query:

Query query = entityManager.createQuery("from Student where id = :id");
query.setParameter("id", studentId);
query.setLockMode(LockModeType.OPTIMISTIC_INCREMENT);
query.getResultList()

6.3. Явная блокировка

Мы можем установить блокировку, вызвав метод блокировки EnitityManager:

Student student = entityManager.find(Student.class, id);
entityManager.lock(student, LockModeType.OPTIMISTIC);

6.4. Обновить

Мы можем вызвать метод обновления так же, как и предыдущий метод:

Student student = entityManager.find(Student.class, id);
entityManager.refresh(student, LockModeType.READ);

6.5. NamedQuery

Последний вариант — использовать @NamedQuery со свойством lockMode:

@NamedQuery(name="optimisticLock",
  query="SELECT s FROM Student s WHERE s.id LIKE :id",
  lockMode = WRITE)

7. OptimisticLockException

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

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

Однако существует рекомендуемый способ обработки описанного исключения. Мы должны снова получить объект, перезагрузив или обновив его. Желательно в новой сделке. После этого мы можем попытаться обновить его еще раз.

8. Заключение

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

Таким образом, это гарантирует, что любые обновления или удаления не будут автоматически перезаписаны или потеряны. В отличие от пессимистической блокировки, он не блокирует объекты на уровне базы данных и, следовательно, не подвержен взаимоблокировкам БД.

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

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

Наконец, исходный код этого руководства доступен на GitHub.