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.
Мы должны знать, что можем получить значение атрибута версии через сущность, но мы не должны обновлять или увеличивать его. Это может сделать только поставщик постоянства, поэтому данные остаются согласованными.
Стоит отметить, что поставщики постоянства могут поддерживать оптимистическую блокировку для сущностей, у которых нет атрибутов версии. Тем не менее, рекомендуется всегда включать атрибуты версии при работе с оптимистичной блокировкой.
Если мы попытаемся заблокировать объект, который не содержит такого атрибута, а поставщик постоянства не поддерживает его, это приведет к PersitenceException
.
5. Режимы блокировки
JPA предоставляет нам два разных режима оптимистичной блокировки (и два псевдонима):
ОПТИМИСТИЧЕСКИЙ
- он получает оптимистическую блокировку чтения для всех объектов, содержащих атрибут версии.OPTIMISTIC_FORCE_INCREMENT
— получает оптимистическую блокировку, такую же, как иOPTIMISTIC
, и дополнительно увеличивает значение атрибута версии.ЧИТАТЬ
– это синонимОПТИМИСТИЧЕСКОГО
WRITE
— это синонимOPTIMISTIC_FORCE_INCREMENT .
Мы можем найти все перечисленные выше типы в классе LockModeType
.
5.1. ОПТИМИСТИЧЕСКИЙ
( ЧИТАТЬ
)
Как мы уже знаем, режимы блокировки OPTIMISTIC
и READ
являются синонимами. Однако спецификация JPA рекомендует использовать OPTIMISTIC
в новых приложениях.
Всякий раз, когда мы запрашиваем режим блокировки OPTIMISTIC
, поставщик постоянства предотвратит грязное чтение наших данных, а также неповторяющееся чтение .
Проще говоря, он должен гарантировать, что ни одна транзакция не сможет зафиксировать какие-либо изменения данных, которые другая транзакция:
- обновил или удалил, но не зафиксировал
- тем временем успешно обновил или удалил
5.2. ОПТИМИСТИЧЕСКИЙ_ИНКРЕМЕНТ
( ЗАПИСАТЬ
)
Как и ранее, OPTIMISTIC_INCREMENT
и WRITE
являются синонимами, но первое предпочтительнее.
OPTIMISTIC_INCREMENT
должен соответствовать тем же условиям, что и режим блокировки OPTIMISTIC .
Кроме того, он увеличивает значение атрибута версии. Однако не указано, следует ли это сделать немедленно или можно отложить до фиксации или сброса.
Стоит знать, что поставщику постоянства разрешено предоставлять функциональность OPTIMISTIC_INCREMENT
, когда запрашивается режим блокировки OPTIMISTIC .
6. Использование оптимистической блокировки
Следует помнить, что для версионных сущностей оптимистическая блокировка доступна по умолчанию. Тем не менее, есть несколько способов запросить его явно.
6.1. Находить
Чтобы запросить оптимистическую блокировку, мы можем передать соответствующий 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. Явная блокировка
Мы можем установить блокировку, вызвав метод блокировки
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 со свойством lockMode
:
@NamedQuery(name="optimisticLock",
query="SELECT s FROM Student s WHERE s.id LIKE :id",
lockMode = WRITE)
7. Исключение оптимистической блокировки
Когда поставщик постоянства обнаруживает конфликты оптимистической блокировки в сущностях, он выдает OptimisticLockException
. Мы должны знать, что из-за исключения активная транзакция всегда помечается для отката.
Полезно знать, как мы можем реагировать на OptimisticLockException
. Для удобства это исключение содержит ссылку на конфликтующую сущность. Однако поставщик сохраняемости не обязан предоставлять его в любой ситуации . Нет гарантии, что объект будет доступен.
Однако существует рекомендуемый способ обработки описанного исключения. Мы должны снова получить объект, перезагрузив или обновив его. Желательно в новой сделке. После этого мы можем попытаться обновить его еще раз.
8. Заключение
В этом руководстве мы познакомились с инструментом, который может помочь нам организовать параллельные транзакции. Оптимистическая блокировка использует атрибуты версии, включенные в объекты, для управления их одновременными изменениями.
Таким образом, это гарантирует, что любые обновления или удаления не будут автоматически перезаписаны или потеряны. В отличие от пессимистической блокировки, он не блокирует объекты на уровне базы данных и, следовательно, не подвержен взаимоблокировкам БД.
Мы узнали, что оптимистическая блокировка включена для версионных сущностей по умолчанию. Однако есть несколько способов запросить его явно, используя различные типы режима блокировки.
Еще один факт, который мы должны помнить, заключается в том, что каждый раз, когда происходят конфликтующие обновления сущностей, мы должны ожидать OptimisticLockException
.
Наконец, исходный код этого руководства доступен на GitHub .