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

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

1. обзор

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

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

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

Однакоthe isolation level is set once the connection is created, и он влияет на каждый оператор в этом соединении. К счастью, мы можем использовать пессимистическую блокировку, которая использует механизмы базы данных для резервирования более детализированного монопольного доступа к данным.

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

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

Мы можем получить эксклюзивные блокировки с помощью операторов «SELECT … FOR UPDATE».

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

Спецификация JPA определяет три режима пессимистической блокировки, которые мы собираемся обсудить:

  • PESSIMISTIC_READ - позволяет нам получить общую блокировку и предотвратить обновление или удаление данных

  • PESSIMISTIC_WRITE - позволяет получить эксклюзивную блокировку и предотвратить чтение, обновление или удаление данных

  • PESSIMISTIC_FORCE_INCREMENT - работает какPESSIMISTIC_WRITE и дополнительно увеличивает атрибут версии версионного объекта

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

Стоит заметить, что за раз мы можем получить только одну блокировку. Если это невозможно, выдаетсяPersistenceException.

2.1. PESSIMISTIC_READс

Всякий раз, когда мы хотим просто читать данные и не сталкиваться с грязными чтениями, мы можем использоватьPESSIMISTIC_READ (разделяемая блокировка). We won’t be able to make any updates or deletes though.

Иногда случается, что используемая нами база данных не поддерживает блокировкуPESSIMISTIC_READ, поэтому возможно, что вместо этого мы получим блокировкуPESSIMISTIC_WRITE.

2.2. PESSIMISTIC_WRITEс

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

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

2.3. PESSIMISTIC_FORCE_INCREMENTс

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

Любому обновлению версионных сущностей может предшествовать получение блокировкиPESSIMISTIC_FORCE_INCREMENT. Acquiring that lock results in updating the version column.

Поставщик сохраняемости должен определить, поддерживает ли онPESSIMISTIC_FORCE_INCREMENT для неверсированных сущностей или нет. Если это не так, он выбрасываетPersistanceException.

2.4. Исключения

При работе с пессимистической блокировкой полезно знать, какое исключение может возникнуть. В спецификацииJPA предусмотрены различные типы исключений:

  • PessimisticLockException - указывает, что получение блокировки или преобразование общей блокировки в эксклюзивную не удается и приводит к откату на уровне транзакции.

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

  • PersistanceException – указывает, что возникла проблема с сохранением. PersistanceException и его подтипы, кромеNoResultException,NonUniqueResultException,LockTimeoutException иQueryTimeoutException, marks the active transaction to be rolled back.

3. Использование пессимистичных блокировок

There are a few possible ways to configure a pessimistic lock on a single record or group of records. Давайте посмотрим, как это сделать в JPA.

3.1. Find

Это, наверное, самый простой способ. Достаточно передать объектLockModeType в качестве параметра методуfind:

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

3.2. запрос

Кроме того, мы также можем использовать объектQuery и вызвать сеттерsetLockMode с режимом блокировки в качестве параметра:

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

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

Также можно вручную заблокировать результаты, полученные методом find:

Student resultStudent = entityManager.find(Student.class, studentId);
entityManager.lock(resultStudent, LockModeType.PESSIMISTIC_WRITE);

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

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

Student resultStudent = entityManager.find(Student.class, studentId);
entityManager.refresh(resultStudent, LockModeType.PESSIMISTIC_FORCE_INCREMENT);

3.5. NamedQuery

Аннотация@NamedQuery также позволяет нам установить режим блокировки:

@NamedQuery(name="lockStudent",
  query="SELECT s FROM Student s WHERE s.id LIKE :studentId",
  lockMode = PESSIMISTIC_READ)

4. Блокировка Scope

Lock scope parameter defines how to deal with locking relationships of the locked entity. Можно получить блокировку только для одного объекта, определенного в запросе, или дополнительно заблокировать его связи.

Для настройки области мы можем использовать перечислениеPessimisticLockScope. Он содержит два значения:NORMAL иEXTENDED.

Мы можем установить область, передав параметр 'javax.persistance.lock.scope' со значениемPessimisticLockScope в качестве аргумента правильному методуEntityManager,Query,TypedQuery илиNamedQueryс:

Map properties = new HashMap<>();
map.put("javax.persistence.lock.scope", PessimisticLockScope.EXTENDED);

entityManager.find(
  Student.class, 1L, LockModeType.PESSIMISTIC_WRITE, properties);

4.1. PessimisticLockScope.NORMALс

We should know that the PessimisticLockScope.NORMAL is the default scope. С помощью этой области блокировки мы блокируем сам объект. При использовании с объединенным наследованием он также блокирует предков.

Давайте посмотрим на пример кода с двумя объектами:

@Entity
@Inheritance(strategy = InheritanceType.JOINED)
public class Person {

    @Id
    private Long id;
    private String name;
    private String lastName;

    // getters and setters
}

@Entity
public class Employee extends Person {

    private BigDecimal salary;

    // getters and setters
}

Когда мы хотим получить блокировкуEmployee, мы можем наблюдать запросSQL, который охватывает эти две сущности:

SELECT t0.ID, t0.DTYPE, t0.LASTNAME, t0.NAME, t1.ID, t1.SALARY
FROM PERSON t0, EMPLOYEE t1
WHERE ((t0.ID = ?) AND ((t1.ID = t0.ID) AND (t0.DTYPE = ?))) FOR UPDATE

4.2. PessimisticLockScope.EXTENDEDс

ОбластьEXTENDED охватывает те же функции, что иNORMAL.. Кроме того,it’s able to block related entities in a join table.

Проще говоря, он работает с объектами, аннотированными@ElementCollection или@OneToOne,@OneToMany и т. Д. с@JoinTable.

Давайте посмотрим на пример кода с аннотацией@ElementCollection:

@Entity
public class Customer {

    @Id
    private Long customerId;
    private String name;
    private String lastName;
    @ElementCollection
    @CollectionTable(name = "customer_address")
    private List
addressList; // getters and setters } @Embeddable public class Address { private String country; private String city; // getters and setters }

Давайте проанализируем некоторые запросы при поиске сущностиCustomer:

SELECT CUSTOMERID, LASTNAME, NAME
FROM CUSTOMER WHERE (CUSTOMERID = ?) FOR UPDATE

SELECT CITY, COUNTRY, Customer_CUSTOMERID
FROM customer_address
WHERE (Customer_CUSTOMERID = ?) FOR UPDATE

Мы видим, что есть два запроса «FOR UPDATE», которые блокируют строку в таблице клиентов, а также строку в таблице соединения.

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

5. Установка таймаута блокировки

Помимо настройки границ блокировки, мы можем настроить еще один параметр блокировки - время ожидания. The timeout value is the number of milliseconds that we want to wait for obtaining a lock until the LockTimeoutException occurs.с

Мы можем изменить значение тайм-аута аналогично блокировкам, используя свойство ‘javax.persistence.lock.timeout' с правильным количеством миллисекунд.

Также можно указать блокировку «без ожидания», изменив значение тайм-аута на ноль. Однако следует иметь в виду, что существуют драйверы баз данных, которыеdon’t support setting a timeout value this way.

Map properties = new HashMap<>();
map.put("javax.persistence.lock.timeout", 1000L);

entityManager.find(
  Student.class, 1L, LockModeType.PESSIMISTIC_READ, properties);

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

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

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

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

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