Рубрики
Без рубрики

Как работает LockModeType.OPTIMISTIC_FORCE_INCREMENT работает в JPA и спящем режиме

Автор оригинала: Vlad Mihalcea.

В моем предыдущем посте я объяснил , как работает ОПТИМИСТИЧНЫЙ Режим блокировки и как он может помочь нам синхронизировать изменения состояния внешней сущности. В этом посте мы собираемся раскрыть шаблоны использования OPTIMISTIC_FORCE_INCREMENT Режима блокировки.

С помощью LockModeType.ОПТИМИСТИЧНО , версия заблокированной сущности проверяется ближе к концу текущей текущей транзакции, чтобы убедиться, что мы не используем устаревшее состояние сущности. Из-за характера проверки на уровне приложения эта стратегия подвержена условиям гонки , поэтому требуется дополнительная пессимистическая блокировка .

Тип LockModeType.OPTIMISTIC_FORCE_INCREMENT не только проверяет ожидаемую версию заблокированной сущности, но и увеличивает ее. И проверка, и обновление выполняются в одном и том же операторе ОБНОВЛЕНИЯ, поэтому используется текущая база данных уровень изоляции транзакций и соответствующие гарантии физической блокировки.

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

В качестве упражнения мы собираемся эмулировать централизованную Систему управления версиями , смоделированную следующим образом:

Репозиторий является корневой сущностью вашей системы, и каждое изменение состояния представлено Фиксацией дочерней сущностью. Каждая Фиксация может содержать один или несколько Изменений компонентов, которые распространяются как один атомарный Единица работы .

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

Во-первых, мы должны проверить, соответствует ли режим OPTIMISTIC_FORCE_INCREMENT Режим блокировки требованиям нашего варианта использования:

doInTransaction(session -> {
	Repository repository = (Repository) session.get(Repository.class, 1L);
	session.buildLockRequest(new LockOptions(LockMode.OPTIMISTIC_FORCE_INCREMENT)).lock(repository);
	Commit commit = new Commit(repository);
	commit.getChanges().add(new Change("README.txt", "0a1,5..."));
	commit.getChanges().add(new Change("web.xml", "17c17..."));
	session.persist(commit);
});

Этот код генерирует следующие выходные данные:

#Alice selects the Repository and locks it using an OPTIMISTIC_FORCE_INCREMENT Lock Mode
Query:{[select lockmodeop0_.id as id1_2_0_, lockmodeop0_.name as name2_2_0_, lockmodeop0_.version as version3_2_0_ from repository lockmodeop0_ where lockmodeop0_.id=?][1]} 

#Alice makes two changes and inserts a new Commit
Query:{[select lockmodeop0_.id as id1_2_0_, lockmodeop0_.name as name2_2_0_, lockmodeop0_.version as version3_2_0_ from repository lockmodeop0_ where lockmodeop0_.id=?][1]} 
Query:{[insert into commit (id, repository_id) values (default, ?)][1]} 
Query:{[insert into commit_change (commit_id, diff, path) values (?, ?, ?)][1,0a1,5...,README.txt]} 
Query:{[insert into commit_change (commit_id, diff, path) values (?, ?, ?)][1,17c17...,web.xml]} 

#The Repository version is bumped up
Query:{[update repository set version=? where id=? and version=?][1,1,0]} 

Наш пользователь выбрал Репозиторий и выпустил новый Коммит . В конце ее транзакции версия Репозитория также увеличивается (поэтому записывается новое изменение состояния Репозитория|/).

Обнаружение конфликтов

В нашем следующем примере у нас будет два пользователя (Алиса и Боб), которые будут одновременно вносить изменения. Чтобы избежать потери обновлений , оба пользователя получают явный OPTIMISTIC_FORCE_INCREMENT Режим блокировки.

Прежде чем Алиса получит возможность совершить фиксацию, Боб только что завершил свою транзакцию и увеличил версию Репозитория . Транзакция Alice будет откатана, что приведет к неустранимому |/исключению StaleObjectStateException .

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

doInTransaction(session -> {
	Repository repository = (Repository) session.get(Repository.class, 1L);
	session.buildLockRequest(new LockOptions(LockMode.OPTIMISTIC_FORCE_INCREMENT)).lock(repository);

	executeSync(() -> {
		doInTransaction(_session -> {
			Repository _repository = (Repository) _session.get(Repository.class, 1L);
			_session.buildLockRequest(new LockOptions(LockMode.OPTIMISTIC_FORCE_INCREMENT)).lock(_repository);
			Commit _commit = new Commit(_repository);
			_commit.getChanges().add(new Change("index.html", "0a1,2..."));
			_session.persist(_commit);
		});
	});

	Commit commit = new Commit(repository);
	commit.getChanges().add(new Change("README.txt", "0a1,5..."));
	commit.getChanges().add(new Change("web.xml", "17c17..."));
	session.persist(commit);
});

Генерируется следующий вывод:

#Alice selects the Repository and locks it using an OPTIMISTIC_FORCE_INCREMENT Lock Mode
Query:{[select lockmodeop0_.id as id1_2_0_, lockmodeop0_.name as name2_2_0_, lockmodeop0_.version as version3_2_0_ from repository lockmodeop0_ where lockmodeop0_.id=?][1]} 

#Bob selects the Repository and locks it using an OPTIMISTIC_FORCE_INCREMENT Lock Mode
Query:{[select lockmodeop0_.id as id1_2_0_, lockmodeop0_.name as name2_2_0_, lockmodeop0_.version as version3_2_0_ from repository lockmodeop0_ where lockmodeop0_.id=?][1]} 

#Bob makes a change and inserts a new Commit
Query:{[insert into commit (id, repository_id) values (default, ?)][1]} 
Query:{[insert into commit_change (commit_id, diff, path) values (?, ?, ?)][1,0a1,2...,index.html]} 

#The Repository version is bumped up to version 1
Query:{[update repository set version=? where id=? and version=?][1,1,0]} 

#Alice makes two changes and inserts a new Commit
Query:{[insert into commit (id, repository_id) values (default, ?)][1]} 
Query:{[insert into commit_change (commit_id, diff, path) values (?, ?, ?)][2,0a1,5...,README.txt]} 
Query:{[insert into commit_change (commit_id, diff, path) values (?, ?, ?)][2,17c17...,web.xml]} 

#The Repository version is bumped up to version 1 and a conflict is raised
Query:{[update repository set version=? where id=? and version=?][1,1,0]} 
INFO  [main]: c.v.h.m.l.c.LockModeOptimisticForceIncrementTest - Failure: 
org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect) : 
[com.vladmihalcea.hibernate.masterclass.laboratory.concurrency.LockModeOptimisticForceIncrementTest$Repository#1]

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

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

Когда изменение состояния дочерней сущности должно вызвать увеличение версии родительской сущности, вам, вероятно, нужен явный режим OPTIMISTIC_FORCE_INCREMENT блокировки.

Код доступен на GitHub .