Статьи

Шаблоны блокировки гибернации — как работает OPTIMISTIC_FORCE_INCREMENT режим блокировки

Вступление

В моем предыдущем посте я объяснил, как работает OPTIMISTIC Lock Mode и как он может помочь нам синхронизировать изменения состояния внешней сущности. В этом посте мы собираемся раскрыть шаблоны использования режима блокировки OPTIMISTIC_FORCE_INCREMENT .

С LockModeType.OPTIMISTIC версия заблокированного объекта проверяется в конце текущей текущей транзакции, чтобы убедиться, что мы не используем устаревшее состояние объекта. Из-за природы проверки уровня приложения эта стратегия чувствительна к условиям гонки, поэтому требуется дополнительная пессимистическая блокировка .

LockModeType.OPTIMISTIC_FORCE_INCREMENT не только проверяет ожидаемую версию заблокированного объекта, но и увеличивает ее. Как проверка, так и обновление выполняются в одном и том же операторе UPDATE, поэтому используются текущий уровень изоляции транзакций базы данных и соответствующие гарантии физической блокировки.

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

Использование централизованного контроля версий

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

repositorycommitchangeoptimisticforceincrement

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

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

Время тестирования

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

01
02
03
04
05
06
07
08
09
10
11
12
doInTransaction(new TransactionCallable<Void>() {
    @Override
    public Void execute(Session 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);
        return null;
    }
});

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

01
02
03
04
05
06
07
08
09
10
11
#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 .

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

explicitlockingoptimisticforceincrement1

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
doInTransaction(new TransactionCallable<Void>() {
    @Override
    public Void execute(Session session) {
        Repository repository = (Repository) session.get(Repository.class, 1L);
        session.buildLockRequest(new LockOptions(LockMode.OPTIMISTIC_FORCE_INCREMENT)).lock(repository);
 
        executeAndWait(new Callable<Void>() {
            @Override
            public Void call() throws Exception {
                return doInTransaction(new TransactionCallable<Void>() {
                    @Override
                    public Void execute(Session _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);
                        return null;
                    }
                });
            }
        });
 
        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);
        return null;
    }
});

Создается следующий вывод:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#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 .