Статьи

Различия в режимах блокировки сущностей JPA

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

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

  1. Транзакция Адама читает данные X
  2. Транзакция Барбары читает данные X
  3. Транзакция Адама изменяет данные X и изменяет их на XA
  4. Транзакция Адама записывает данные XA
  5. Транзакция Барбары изменяет данные X и изменяет их на XB
  6. Транзакция Барбары пишет данные XB

В результате изменения, сделанные Адамом, полностью исчезли и были перезаписаны Барбарой, и она даже не заметила этого. Подобный сценарий иногда называют « грязным чтением» . Очевидно, что желаемый результат заключается в том, что Адам пишет XA, а Барбара вынуждена просматривать изменения XA, прежде чем писать XB.

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

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

С оптимистической блокировкой, вот возможный сценарий для Адама и Барбары:

  1. Транзакция Адама читает данные X
  2. Транзакция Барбары читает данные X
  3. Транзакция Адама изменяет данные X и изменяет их на XA
  4. Транзакция Адама записывает данные XA
  5. Транзакция Барбары изменяет данные X и изменяет их на XB
  6. Транзакция Барбары пытается записать данные XB, но получает и ошибку
  7. Барбаре нужно прочитать данные XA (или начать совершенно новую транзакцию)
  8. Транзакция Барбары изменяет данные XA и изменяет их на XAB
  9. Транзакция Барбары пишет данные XAB

Как вы видите, Барбара вынуждена просматривать изменения Адама, и, если она решит, она может изменить изменения Адама и сохранить их (объединить изменения). Окончательные данные содержат изменения как Адама, так и Барбары.

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

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

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

С пессимистичными блокировками сценарий может быть таким:

  1. Транзакция Адама читает данные X
  2. Транзакция Адама блокирует X
  3. Транзакция Барбары хочет прочитать данные X, но ждет, пока X уже заблокирован
  4. Транзакция Адама изменяет данные X и изменяет их на XA
  5. Транзакция Адама записывает данные XA
  6. Транзакция Барбары читает данные XA
  7. Транзакция Барбары изменяет данные XA и изменяет их на XAB
  8. Транзакция Барбары пишет данные XAB

Как мы видим, Барбаре снова приходится писать XAB, в котором также содержатся изменения Адама. Однако решение полностью отличается от оптимистического сценария — Барбаре нужно дождаться завершения транзакции Адама, прежде чем читать данные. Кроме того, нам нужно выполнить команду блокировки вручную в обеих транзакциях, чтобы сценарий работал. (Поскольку мы не уверены, какая транзакция будет обработана первой, Адама или Барбары, обе транзакции должны заблокировать данные перед их изменением). Оптимистическая блокировка требует больше настройки, чем пессимистическая блокировка, с колонкой версии, необходимой для каждой сущности, но тогда нам не нужно запомнить выдачу блокировок в транзакциях. JPA выполняет все проверки автоматически, нам нужно обрабатывать только возможные исключения.

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

Даже в спецификации JPA говорится, что указывать PESSIMISTIC_READ не требуется (так как многие БД поддерживают только блокировки WRITE):

Для реализации допустимо использовать LockModeType.PESSIMISTIC_WRITE где был запрошен LockModeType.PESSIMISTIC_READ , но не наоборот.

Список доступных типов блокировки в JPA

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

  1. LockModeType.Optimistic
    • Это действительно по умолчанию. Обычно игнорируется, как указано в ObjectDB . По моему мнению, он существует только для того, чтобы вы могли динамически вычислять режим блокировки и передавать его дальше, даже если блокировка в конечном итоге будет ОПТИМАЛЬНОЙ. Хотя это не очень вероятный вариант использования, но это всегда хороший дизайн API, позволяющий ссылаться даже на значение по умолчанию.
    • Пример: Java
      1
      2
      LockModeType lockMode = resolveLockMode();
      A a = em.find(A.class, 1, lockMode);
  2. LockModeType.OPTIMISTIC_FORCE_INCREMENT
    • Это редко используемая опция. Но это может быть разумно, если вы хотите заблокировать ссылку на эту сущность другой сущностью. Другими словами, вы хотите заблокировать работу с сущностью, даже если она не изменена, но другие сущности могут быть изменены по отношению к этой сущности.
    • Пример:
      • У нас есть сущность Книга и Полка. Можно добавить Книгу на полку, но книга не имеет ссылки на ее полку. Целесообразно заблокировать действие по перемещению книги на полку, чтобы книга не оказалась на 2 полках. Чтобы заблокировать это действие, недостаточно заблокировать текущую сущность книжной полки, поскольку книга еще не должна быть на полке. Также не имеет смысла блокировать все целевые книжные полки, поскольку они могут быть разными в разных транзакциях. Единственное, что имеет смысл, — это заблокировать сам объект книги, даже если в нашем случае он не изменяется (он не содержит ссылки на свою книжную полку).
  3. LockModeType.PESSIMISTIC_READ
    • этот режим похож на LockModeType.PESSIMISTIC_WRITE , но отличается в одном: до тех пор, пока какая-либо транзакция не заблокирует запись в одной и той же сущности, она не должна блокировать чтение этой сущности. Это также позволяет другим транзакциям блокироваться с помощью LockModeType.PESSIMISTIC_READ . Различия между блокировками WRITE и READ хорошо объяснены здесь (ObjectDB) и здесь (OpenJPA) . Однако очень часто это ведет себя как LockModeType.PESSIMISTIC_WRITE , поскольку спецификация позволяет это, и многие провайдеры не реализуют его отдельно.
  4. LockModeType.PESSIMISTIC_WRITE
    • это более сильная версия LockModeType.PESSIMISTIC_READ . Когда блокировка WRITE установлена, JPA с помощью базы данных предотвратит любую другую транзакцию для чтения объекта, а не только для записи, как при блокировке READ .
  5. LockModeType.PESSIMISTIC_FORCE_INCREMENT
    • это еще один редко используемый режим блокировки. Тем не менее, это вариант, когда вам нужно объединить PESSIMISTIC и PESSIMISTIC механизмы. Использование простого PESSIMISTIC_WRITE приведет к PESSIMISTIC_WRITE в следующем сценарии:
      1. транзакция A использует оптимистическую блокировку и читает объект E
      2. транзакция B получает блокировку WRITE для объекта E
      3. транзакция B фиксирует и снимает блокировку E
      4. транзакция A обновляет E и фиксирует
    • на шаге 4, если столбец версии не увеличивается с помощью транзакции B, ничто не мешает A перезаписать изменения B. Изменение режима блокировки. LockModeType.PESSIMISTIC_FORCE_INCREMENT заставит транзакцию B обновить номер версии и вызвать сбой транзакции A с OptimisticLockException , даже если B использовал пессимистическая блокировка.

Для выдачи блокировки определенного типа JPA предоставляет следующие средства:

Вы можете использовать любой из двух типов механизмов блокировки в JPA. Также возможно смешать их при необходимости, если вы используете пессимистическую блокировку типа PESSIMISTIC_FORCE_INCREMENT .