Оптимистическая блокировка в JPA

Оптимистическая блокировка в JPA

1. Вступление

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

Более того, нам необходимо обеспечить согласованность данных при одновременном чтении и обновлении.

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

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

Чтобы использовать оптимистическую блокировку,we need to have an entity including a property with @Version annotation. При его использовании каждая транзакция, которая читает данные, содержит значение свойства version.

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

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

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

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

О пессимистических блокировках мы рассказываем в одной из наших предыдущих статей -Pessimistic Locking in JPA. Давайте выясним, в чем разница и какие преимущества мы можем получить от каждого типа блокировки.

Как мы уже говорили,optimistic locking is based on detecting changes on entities by checking their version attribute. Если происходит какое-либо одновременное обновление, происходитOptmisticLockException. После этого мы можем повторить попытку обновления данных.

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

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

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

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

Атрибуты версии - это свойства с аннотацией@Version. They are necessary for enabling optimistic locking. Давайте посмотрим на образец класса сущности:

@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с

We should know that we can retrieve a value of the version attribute via entity, but we mustn’t update or increment it. Это может сделать только провайдер сохраняемости, поэтому данные остаются согласованными.

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

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

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

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

  • OPTIMISTIC - получает оптимистичную блокировку чтения для всех сущностей, содержащих атрибут версии

  • OPTIMISTIC_FORCE_INCREMENT - получает оптимистическую блокировку, такую ​​же, какOPTIMISTIC, и дополнительно увеличивает значение атрибута версии

  • READ - это синонимOPTIMISTIC

  • WRITE - это синонимOPTIMISTIC_FORCE_INCREMENT

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

5.1. OPTIMISTIC (READ)

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

Whenever we request the OPTIMISTIC lock mode, a persistence provider will prevent our data from dirty reads as well as non-repeatable reads.

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

  • обновлен или удален, но не зафиксирован

  • обновлен или успешно удален

5.2. OPTIMISTIC_INCREMENT (WRITE)

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

OPTIMISTIC_INCREMENT должен соответствовать тем же условиям, что и режим блокировкиOPTIMISTIC. Additionally, it increments the value of a version attribute. Однако не указано, следует ли это сделать немедленно или можно отложить до фиксации или сброса.

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

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

We should remember that for versioned entities optimistic locking is available by default. Тем не менее, есть несколько способов явно запросить его.

6.1. Find

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

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

6.2. запрос

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

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

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

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

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

6.4. обновление

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

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с

When the persistence provider discovers optimistic locking conflicts on entities, it throws OptimisticLockException Мы должны знать, что из-за исключения активная транзакция всегда помечается для отката.

Приятно знать, как мы можем реагировать наOptimisticLockException. Удобно, что это исключение содержит ссылку на конфликтующий объект. However, it’s not mandatory for the persistence provider to supply it in every situation. Нет гарантии, что объект будет доступен.

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

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

В этом уроке мы познакомились с инструментом, который может помочь нам организовать параллельные транзакции. Optimistic locking uses version attributes included in entities to control concurrent modifications on them.с

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

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

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

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