休眠锁定模式– PESSIMISTIC_FORCE_INCREMENT锁定模式如何工作
介紹
在我以前的文章中 ,我介紹了OPTIMISTIC_FORCE_INCREMENT鎖定模式,并將其應用于將子實體版本更改傳播到鎖定的父實體。 在本文中,我將介紹PESSIMISTIC_FORCE_INCREMENT鎖定模式,并將其與樂觀的鎖定模式進行比較。
相像多于不同
正如我們已經發現的那樣,即使當前事務沒有修改鎖定的實體狀態, OPTIMISTIC_FORCE_INCREMENT鎖定模式也可以增加實體版本。 對于每種鎖定模式,Hibernate定義一個關聯的LockingStrategy ,并且OPTIMISTIC_FORCE_INCREMENT鎖定模式事件由OptimisticForceIncrementLockingStrategy處理:
public class OptimisticForceIncrementLockingStrategy implements LockingStrategy {//code omitted for brevity@Overridepublic void lock(Serializable id, Object version, Object object, int timeout, SessionImplementor session) {if ( !lockable.isVersioned() ) {throw new HibernateException( "[" + lockMode + "] not supported for non-versioned entities [" + lockable.getEntityName() + "]" );}final EntityEntry entry = session.getPersistenceContext().getEntry( object );// Register the EntityIncrementVersionProcess action to run just prior to transaction commit.( (EventSource) session ).getActionQueue().registerProcess( new EntityIncrementVersionProcess( object, entry ) );} }此策略在當前持久性上下文操作隊列中注冊EntityIncrementVersionProcess 。 在完成當前正在運行的事務之前,鎖定的實體版本會增加。
public class EntityIncrementVersionProcess implements BeforeTransactionCompletionProcess {//code omitted for brevity@Overridepublic void doBeforeTransactionCompletion(SessionImplementor session) {final EntityPersister persister = entry.getPersister();final Object nextVersion = persister.forceVersionIncrement( entry.getId(), entry.getVersion(), session );entry.forceLocked( object, nextVersion );} }與OPTIMISTIC_FORCE_INCREMENT相似 , PESSIMISTIC_FORCE_INCREMENT鎖定模式由PessimisticForceIncrementLockingStrategy處理:
public class PessimisticForceIncrementLockingStrategy implements LockingStrategy {//code omitted for brevity@Overridepublic void lock(Serializable id, Object version, Object object, int timeout, SessionImplementor session) {if ( !lockable.isVersioned() ) {throw new HibernateException( "[" + lockMode + "] not supported for non-versioned entities [" + lockable.getEntityName() + "]" );}final EntityEntry entry = session.getPersistenceContext().getEntry( object );final EntityPersister persister = entry.getPersister();final Object nextVersion = persister.forceVersionIncrement( entry.getId(), entry.getVersion(), session );entry.forceLocked( object, nextVersion );} }鎖定的實體立即增加,因此這兩種鎖定模式執行相同的邏輯,但時間不同。 PESSIMISTIC_FORCE_INCREMENT的命名可能使您想到您正在使用悲觀的鎖定策略,而實際上,此鎖定模式只是一個樂觀的鎖定變體。
悲觀鎖需要顯式物理鎖(共享或獨占),而樂觀鎖則依賴于當前事務隔離級別的隱式鎖。
存儲庫用例
我將重用之前的練習,然后切換到使用PESSIMISTIC_FORCE_INCREMENT鎖定模式。 回顧一下,我們的域模型包含:
- 一個存儲庫實體,其版本隨每次新的提交而增加
- 一個Commit實體,封裝了一個原子存儲庫狀態轉換
- 一個CommitChange組件,封裝了一個存儲庫資源更改
防止并行修改
愛麗絲和鮑勃同時訪問我們的系統。 從數據庫中獲取存儲庫實體后,它總是被鎖定:
private final CountDownLatch startLatch = new CountDownLatch(1); private final CountDownLatch endLatch = new CountDownLatch(1);@Test public void testConcurrentPessimisticForceIncrementLockingWithLockWaiting() throws InterruptedException {LOGGER.info("Test Concurrent PESSIMISTIC_FORCE_INCREMENT Lock Mode With Lock Waiting");doInTransaction(new TransactionCallable<Void>() {@Overridepublic Void execute(Session session) {try {Repository repository = (Repository) session.get(Repository.class, 1L);session.buildLockRequest(new LockOptions(LockMode.PESSIMISTIC_FORCE_INCREMENT)).lock(repository);executeNoWait(new Callable<Void>() {@Overridepublic Void call() throws Exception {return doInTransaction(new TransactionCallable<Void>() {@Overridepublic Void execute(Session _session) {LOGGER.info("Try to get the Repository row");startLatch.countDown();Repository _repository = (Repository) _session.get(Repository.class, 1L);_session.buildLockRequest(new LockOptions(LockMode.PESSIMISTIC_FORCE_INCREMENT)).lock(_repository);Commit _commit = new Commit(_repository);_commit.getChanges().add(new Change("index.html", "0a1,2..."));_session.persist(_commit);_session.flush();endLatch.countDown();return null;}});}});startLatch.await();LOGGER.info("Sleep for 500ms to delay the other transaction PESSIMISTIC_FORCE_INCREMENT Lock Mode acquisition");Thread.sleep(500);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;} catch (InterruptedException e) {fail("Unexpected failure");}return null;}});endLatch.await(); }該測試用例生成以下輸出:
#Alice selects the Repository 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 locks the Repository using a PESSIMISTIC_FORCE_INCREMENT Lock Mode Query:{[update repository set version=? where id=? and version=?][1,1,0]} #Bob tries to get the Repository but the SELECT is blocked by Alice lock INFO [pool-1-thread-1]: c.v.h.m.l.c.LockModePessimisticForceIncrementTest - Try to get the Repository row#Alice sleeps for 500ms to prove that Bob is waiting for her to release the acquired lock Sleep for 500ms to delay the other transaction PESSIMISTIC_FORCE_INCREMENT Lock Mode acquisition#Alice makes two changes and inserts a new Commit<a href="https://vladmihalcea.files.wordpress.com/2015/02/explicitlockingpessimisticforceincrementfailfast.png"><img src="https://vladmihalcea.files.wordpress.com/2015/02/explicitlockingpessimisticforceincrementfailfast.png?w=585" alt="ExplicitLockingPessimisticForceIncrementFailFast" width="585" height="224" class="alignnone size-large wp-image-3955" /></a> 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]#The Repository version is bumped up to version 1 and a conflict is raised Query:{[insert into commit_change (commit_id, diff, path) values (?, ?, ?)][1,17c17...,web.xml]} Query:{[update repository set version=? where id=? and version=?][1,1,0]}#Alice commits the transaction, therefore releasing all locks DEBUG [main]: o.h.e.t.i.j.JdbcTransaction - committed JDBC Connection#Bob Repository SELECT can proceed Query:{[select lockmodepe0_.id as id1_2_0_, lockmodepe0_.name as name2_2_0_, lockmodepe0_.version as version3_2_0_ from repository lockmodepe0_ where lockmodepe0_.id=?][1]} #Bob can insert his changes Query:{[update repository set version=? where id=? and version=?][2,1,1]} Query:{[insert into commit (id, repository_id) values (default, ?)][1]} Query:{[insert into commit_change (commit_id, diff, path) values (?, ?, ?)][2,0a1,2...,index.html]}下圖可以很容易地看到這個鎖定過程:
每當修改數據庫行時, HSQLDB測試數據庫“ 兩階段鎖定”實現都會使用過程粒度表鎖定。
這就是Bob不能獲得Alice剛剛更新的Repository數據庫行上的讀取鎖的原因。 其他數據庫(例如Oracle,PostgreSQL)使用MVCC ,因此允許SELECT繼續執行(使用當前的修改事務撤消日志來重新創建前一個行狀態),同時阻止沖突的數據修改語句(例如,當其他并發事務已經停止時更新存儲庫行)尚未提交鎖定的實體狀態更改)。
快速失敗
即時版本增加具有一些有趣的好處:
- 如果版本UPDATE成功(獲取了排他級鎖),則其他任何并發事務都無法修改鎖定的數據庫行。 這是將邏輯鎖(版本遞增)升級為物理鎖(數據庫互斥鎖)的時刻。
- 如果版本UPDATE失敗(因為其他一些并發事務已經提交了版本更改),那么我們當前正在運行的事務可以立即回滾(而不是在提交期間等待事務失敗)
后一個用例可以如下所示:
對于這種情況,我們將使用以下測試用例:
@Test public void testConcurrentPessimisticForceIncrementLockingFailFast() throws InterruptedException {LOGGER.info("Test Concurrent PESSIMISTIC_FORCE_INCREMENT Lock Mode fail fast");doInTransaction(new TransactionCallable<Void>() {@Overridepublic Void execute(Session session) {try {Repository repository = (Repository) session.get(Repository.class, 1L);executeAndWait(new Callable<Void>() {@Overridepublic Void call() throws Exception {return doInTransaction(new TransactionCallable<Void>() {@Overridepublic Void execute(Session _session) {Repository _repository = (Repository) _session.get(Repository.class, 1L);_session.buildLockRequest(new LockOptions(LockMode.PESSIMISTIC_FORCE_INCREMENT)).lock(_repository);Commit _commit = new Commit(_repository);_commit.getChanges().add(new Change("index.html", "0a1,2..."));_session.persist(_commit);_session.flush();return null;}});}});session.buildLockRequest(new LockOptions(LockMode.PESSIMISTIC_FORCE_INCREMENT)).lock(repository);fail("Should have thrown StaleObjectStateException!");} catch (StaleObjectStateException expected) {LOGGER.info("Failure: ", expected);}return null;}}); }生成以下輸出:
#Alice selects the Repository 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 too Query:{[select lockmodepe0_.id as id1_2_0_, lockmodepe0_.name as name2_2_0_, lockmodepe0_.version as version3_2_0_ from repository lockmodepe0_ where lockmodepe0_.id=?][1]} #Bob locks the Repository using a PESSIMISTIC_FORCE_INCREMENT Lock Mode Query:{[update repository set version=? where id=? and version=?][1,1,0]} #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]} #Bob commits the transaction DEBUG [pool-3-thread-1]: o.h.e.t.i.j.JdbcTransaction - committed JDBC Connection#Alice tries to lock the Repository Query:{[update repository set version=? where id=? and version=?][1,1,0]} #Alice cannot lock the Repository, because the version has changed INFO [main]: c.v.h.m.l.c.LockModePessimisticForceIncrementTest - Failure: org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect) : [com.vladmihalcea.hibernate.masterclass.laboratory.concurrency.LockModePessimisticForceIncrementTest$Repository#1]結論
像OPTIMISTIC_FORCE_INCREMENT一樣, PESSIMISTIC_FORCE_INCREMENT鎖定模式對于將實體狀態更改傳播到父實體非常有用。
盡管鎖定機制相似,但是PESSIMISTIC_FORCE_INCREMENT可以當場應用,從而允許當前運行的事務即時評估鎖定結果。
- 代碼可在GitHub上獲得 。
翻譯自: https://www.javacodegeeks.com/2015/02/hibernate-locking-patterns-pessimistic_force_increment-lock-mode-work.html
總結
以上是生活随笔為你收集整理的休眠锁定模式– PESSIMISTIC_FORCE_INCREMENT锁定模式如何工作的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 具有Spring Boot和数据功能的J
- 下一篇: 实用程序类与函数式编程无关