Явные блокировки в Spring Data Jpa

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

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

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

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

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

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

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

@Lock

Режим блокировки задаётся аннотацией @Lock, которая добавляется к методу репозитория. Добавлять её можно к любому методу, кроме методов с собственным кодом. В параметре к аннотации передаётся требуемый режим блокировки:

  • LockModeType.OPTIMISTIC оптимистичная блокировка на чтение: если при завершении транзакции кто-то извне изменит поле @Version, то транзакция автоматически будет откачена и будет выброшено OptimisticLockException.
  • LockModeType.OPTIMISTIC_FORCE_INCREMENT оптимистичная блокировка на запись. Ведёт себя как и блокировка на чтение, но при этом увеличивает значение поля @Version.
  • LockModeType.PESSIMISTIC_READ — данные блокируются в момент чтения и это гарантирует, что никто в ходе выполнения транзакции не сможет их изменить. Остальные транзакции, тем не менее, смогут параллельно читать эти данные. Использование этой блокировки может вызывать долгое ожидание блокировки или даже выкидывание OptimisticLockException.
  • LockModeType.PESSIMISTIC_WRITE — данные блокируются в момент записи и никто с момента захвата блокировки не может в них писать и не может их читать до окончания транзакции, владеющей блокировкой. Использование этой блокировки может вызывать долгое ожидание блокировки. 

    Кроме того, для сущностей с  @Version существует третий вариант пессимистичной блокировки:

  • LockModeType.PESSIMISTIC_FORCE_INCREMENT — ведёт себя как  LockModeType.PESSIMISTIC_READ, но в конце транзакции увеличивает значение поля @Version, даже если фактически сущность не изменилась.