Документация разработчика Hibernate – Глава V. Блокировки
Содержание
 5.1. Оптимистичные блокировки
   5.1.1 Выделенный номер версии
   5.1.2. Timestamp
 5.2. Пессимистичные блокировки
Блокировки – это меры по предотвращению модификации данных в реляционной базе данных между временем их чтения, и временем их использования.
Стратегия блокировок может быть либо оптимистичной, либо пессимистичной.
Оптимистичная
Оптимистичные блокировки предполагают, что множество транзакций могут завершиться без влияния друг на друга, и таким образом могут выполнятся без блокировок тех ресурсов, на которые они влияют. Перед коммитом, каждая транзакция проверяет, что ни одна другая транзакция не модифицировала ее данные. Если проверка выявила конфликтующие модификации, транзакция, находящаяся в состоянии коммита, откатывается.
Пессимистичная
Пессимистичная стратегия подразумевает, что параллельные транзакции будут конфликтовать каждая друг с другом, и требует блокировки ресурсов после их чтения, а также ее снятия только после того, как приложение завершило использование данных.
Hibernate предоставляет механизмы для реализации обеих стратегий блокировок в вашем приложении.
5.1. Оптимистичные блокировки
Вы можете хранить версионированные данные, когда ваше приложение использует долгоживущие транзакции или диалоги, покрывающие несколько БД-транзакций. Таким образом, если одна и та же сущность будет модифицироваться двумя диалогами, последний диалог, коммитивший изменения, будет оповещен о конфликте, и не перезапишет результаты другого диалога. Этот подход гарантирует некоторую степень изоляции, но при этом хорошо масштабируется, и довольно неплохо себя показывает в ситуациях Read-Often Write-Sometimes
Hibernate предоставляет два различных механизма для хранения версионной информации – выделенный номер версии, или временную метку (timestamp).
Номер версии
Временная метка
Свойство версии или временной метки не может быть null для отсоединенных (detached) объектов. Hibernate распознает любой экземпляр с версией ( или временной меткой) равной null как transient, в независимости от других стратегий unsaved-value* которые вы указываете. Объявление null-ового свойства версии или временной метки – легкий способ избежать проблемы с транзитивным повторным соединением(transitive reattachment) в Hibernate, являющееся особенно полезным в случаях, где вы используете присоединенные (assigned) идентификаторы или композитные ключи.
* unsaved-value – стратегия определения операции UPDATE или INSERT для синхронизации с БД, зависящая от значения свойства, проецирующегося с помощью id, version, или timestamp (прим. перев.)
5.1.1. Выделенный номер версии
Механизм номера версии для оптимистичных блокировок предоставляется аннотацией Version.
Пример 5.1. Аннотация Version
@Entity public class Flight implements Serializable < . @Version @Column(name="OPTLOCK") public Integer getVersion() < . >>
Здесь свойство версии маппится на колонку OPTLOCK, а менеджер сущностей (entity manager) использует ее для выявления конфликтующих обновлений, и предотвращения потери обновлений, которые были бы перезаписаны стратегией last-commit-wins
Колонка версии может быть любого типа, при условии, что вы определите и реализуете подходящий UserVersionType.
Вашему приложению запрещено изменять номер версии, проставленный Hibernate. Чтобы искусственно увеличить номер версии, см. описание свойств LockModeType.OPTIMISTIC_FORCE_INCREMENT или LockModeType.PESSIMISTIC_FORCE_INCREMENT в документации по Hibernate Entity Manager. Если номер версии сгенерирован базой данных, например триггером, используйте аннотацию org.hibernate.annotations.Generated(GenerationTime.ALWAYS).
Пример 5.2. Объявление свойства версии в hbm.xml
Имя | Описание |
---|---|
column | Имя колонки, в которой находится номер версии. Опционально, по-умолчанию такое же как у имени свойства. |
name | Имя свойства персистентного класса. |
type | Тип номера версии. Опционально, по-умолчанию integer. |
access | Стратегия Hibernate для доступа к значению свойства. Опционально, по-умолчанию property |
unsaved-value | Показывает, что экземпляр только что создан и тем самым не сохранен. Выделяет из отсоединенных сущностей (detached). Значение по-умолчанию, undefined, показывает, что свойство-идентификатор не должно использоваться. Опционально. |
generated | Показывает, что свойство версии должно генерироваться базой данных. Опционально, по-умолчанию never. |
insert | Включать или нет колонку версии в выражение SQL-insert. По-умолчанию true, but вы можете поставить это в false если колонка в БД определена со значением по-умолчанию 0 |
5.1.2. Timestamp
Временные метки (timestamps) — менее надежный способ оптимистичных блокировок чем номера версий, который также может быть использован приложениями для других целей. Временные метки используются автоматически, если вы используете аннотацию Version на свойстве типа Date или Calendar.
Пример 5.3. Использование временных меток для оптимистичных блокировок
@Entity public class Flight implements Serializable < . @Version public Date getLastUpdate() < . >>
Hibernate может извлечь значение временной метки из базы данных или JVM, прочитав значение аннотации org.hibernate.annotations.Source. Значение может быть либо org.hibernate.annotations.SourceType.DB, либо org.hibernate.annotations.SourceType.VM. Поведение по-умолчанию – это использование БД, также используемое, если вы не укажете аннотацию.
Временная метка также может быть сгенерирована базой данных вместо Hibernate, если вы используете аннотацию org.hibernate.annotations.Generated(GenerationTime.ALWAYS).
Пример 5.4. Элемент timestamp в hbm.xml
Имя | Описание |
---|---|
column | Имя колонки, в которой находится временная метка. Опционально, по-умолчанию такое же, как и имя свойства. |
name | Имя JavaBeans-свойства типа Date или Timestamp у персистентного свойства. |
access | Стратегия, которую Hibernate использует для доступа к значению свойства. Опционально, по-умолчанию property. |
unsaved-value | Показывает, что экземпляр только что создан и тем самым не сохранен. Выделяет из отсоединенных сущностей (detached). Значение по-умолчанию, undefined, показывает что свойство-идентификатор не должно использоваться. Опционально. |
source | Извлекает ли Hibernate метку из БД или из текущей JVM. БД-метки вносят дополнительный оверхэд, т.к Hibernate нужно запрашивать БД каждый раз для определения инкремента. Однако, БД-метки более безопасны при использовании в кластеризованном окружении. Не все диалекты БД поддерживают извлечение текущих временных меток из БД. Другие могут быть небезопасны для блокировок, из-за нехватки точности. |
generated | Генерируется ли метки средствами БД. Опционально, по-умолчанию never. |
5.2. Пессимистичные блокировки
Класс LockMode определяет различные уровни блокировок, которые может захватывать Hibernate.
- LockMode.WRITE
Захватывается автоматически, когда Hibernate обновляет или вставляет строку. - LockMode.UPGRADE
Захватывается после явного запроса пользователя с использованием SELECT… FOR UPDATE на БД, поддерживающих данный синтаксис. - LockMode.UPGRADE_NOWAIT
Захватывается после явного запроса пользователя с использованием SELECT… FOR UPDATE NOWAIT в Oracle - LockMode.READ
Захватывается автоматически когда Hibernate читает данные под уровнями изоляции Repeatable Read или Serializable. Может быть повторно захвачен явным запросом пользователя. - LockMode.NONE
Отсутствие блокировки. Все объекты переключаются на этот режим блокировки в конце транзакции. Объекты, ассоциированные с сессией
через вызов update() или saveOrUpdate также начинают в этом режиме .
- Вызов Session.load(), с указанием LockMode
- Вызов Session.lock()
- Вызов Query.setLockMode()
Оптимистические и Пессимистические Блокировки
Краткий обзор оптимистических и пессимистических блокировок. В этой заметке рассматриваются основные различия этих двух подходов на примере реализации целочисленного счетчика на Java .
Статья из моего telegram канала: Senior’s Blog. Подписывайтесь на канал 😉
Блокировки
Блокировки в многопоточной среде необходимы для одновременной работы двух и более потоков с одними и теми же данными. Блокировки позволяют избежать потери и повреждения данных. Существуют два подхода к блокировкам: оптимистические и пессимистические.
Суть пессимистических блокировок в эксклюзивном доступе к данным, т.е. когда один поток получил пессимистическую блокировку на данные, другие потоки не могут читать и изменять эти данные, пока поток не снимет блокировку. Пессимистические блокировки достаточно простые в реализации, но обладают очень важным недостатком — дедлоками (deadlock).
Оптимистические блокировки устроены по другому принципу. Они существуют во многих вариациях, я расскажу про самую простую — CAS. Когда два потока хотят изменить одни и те же данные, потоки копируют эти данные в свою локальную память, затем меняют их и пытаются отправить измененные данные в основную память. Перед внесением изменений в основную память проверяется версия данных или предыдущее значение данных. Если проверка не проходит, то поток снова копирует к себе уже новые данные, вносит в них изменения и снова пытается отправить эти измененные данные в основную память.
Оптимистические блокировки иногда называют не блокируемыми (non-blocking), потому что при изменении данных поток работает со своей локальной копией данных, а данные в основном хранилище остаются открытыми другим потоках для чтения и изменения.
Простой пример оптимистических блокировок с которыми каждый день сталкивается любой разработчик — это системы контроля версий, например Git. Вы копируете себе ветку с кодом, модифицируете ее и пытаетесь слить с мастером, если кто-то обновил мастер до вас, вам надо скопировать себе его изменения, исправить конфликты и снова попытаться слить свою ветку с мастером.
В этой статье сравниваются эти два вида блокировок на примере баз данных.
Java
Рассмотрим реализацию оптимистических и пессимистических блокировок в Java . В качестве примера реализуем целочисленный счетчик.
Для пессимистических блокировок наиболее простым решением будет использование блока synchronized .
Получаем простенький класс:
Не забываем, что synchronize нужен не только при записи но и при чтении счетчика. И в этом случае counter необязательно объявлять как volatile , потому что synchronized гарантирует синхронизацию данных в оперативной памяти и в памяти потока при входе в блок. Если интересна эта тема почитайте про Happens Before Guarantee , например тут.
Для оптимистических блокировок все уже сделано за нас — это AtomicInteger . Если посмотреть на код AtomicInteger , то там обнаружится обычная volatile переменная
И функция которая обновляет эту переменную с помощью Compare-and-swap (CAS) алгоритма:
Эта функция будет использовать нативную инструкцию процессора, которая атомарно выполнит сравнение и обновление значения переменной.
Подробнее про реализацию CAS в JVM можно почитать тут.