休眠锁定模式– PESSIMISTIC_FORCE_INCREMENT锁定模式如何工作
介紹
在我以前的文章中 ,我介紹了OPTIMISTIC_FORCE_INCREMENT鎖定模式,并將其應(yīng)用于將子實(shí)體版本更改傳播到鎖定的父實(shí)體。 在本文中,我將介紹PESSIMISTIC_FORCE_INCREMENT鎖定模式,并將其與樂觀的鎖定模式進(jìn)行比較。
相像多于不同
正如我們已經(jīng)發(fā)現(xiàn)的那樣,即使當(dāng)前事務(wù)沒有修改鎖定的實(shí)體狀態(tài), OPTIMISTIC_FORCE_INCREMENT鎖定模式也可以增加實(shí)體版本。 對(duì)于每種鎖定模式,Hibernate定義一個(gè)關(guān)聯(lián)的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 ) );} }此策略在當(dāng)前持久性上下文操作隊(duì)列中注冊(cè)EntityIncrementVersionProcess 。 在完成當(dāng)前正在運(yùn)行的事務(wù)之前,鎖定的實(shí)體版本會(huì)增加。
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 );} }鎖定的實(shí)體立即增加,因此這兩種鎖定模式執(zhí)行相同的邏輯,但時(shí)間不同。 PESSIMISTIC_FORCE_INCREMENT的命名可能使您想到您正在使用悲觀的鎖定策略,而實(shí)際上,此鎖定模式只是一個(gè)樂觀的鎖定變體。
悲觀鎖需要顯式物理鎖(共享或獨(dú)占),而樂觀鎖則依賴于當(dāng)前事務(wù)隔離級(jí)別的隱式鎖。
存儲(chǔ)庫用例
我將重用之前的練習(xí),然后切換到使用PESSIMISTIC_FORCE_INCREMENT鎖定模式。 回顧一下,我們的域模型包含:
- 一個(gè)存儲(chǔ)庫實(shí)體,其版本隨每次新的提交而增加
- 一個(gè)Commit實(shí)體,封裝了一個(gè)原子存儲(chǔ)庫狀態(tài)轉(zhuǎn)換
- 一個(gè)CommitChange組件,封裝了一個(gè)存儲(chǔ)庫資源更改
防止并行修改
愛麗絲和鮑勃同時(shí)訪問我們的系統(tǒng)。 從數(shù)據(jù)庫中獲取存儲(chǔ)庫實(shí)體后,它總是被鎖定:
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]}下圖可以很容易地看到這個(gè)鎖定過程:
每當(dāng)修改數(shù)據(jù)庫行時(shí), HSQLDB測試數(shù)據(jù)庫“ 兩階段鎖定”實(shí)現(xiàn)都會(huì)使用過程粒度表鎖定。
這就是Bob不能獲得Alice剛剛更新的Repository數(shù)據(jù)庫行上的讀取鎖的原因。 其他數(shù)據(jù)庫(例如Oracle,PostgreSQL)使用MVCC ,因此允許SELECT繼續(xù)執(zhí)行(使用當(dāng)前的修改事務(wù)撤消日志來重新創(chuàng)建前一個(gè)行狀態(tài)),同時(shí)阻止沖突的數(shù)據(jù)修改語句(例如,當(dāng)其他并發(fā)事務(wù)已經(jīng)停止時(shí)更新存儲(chǔ)庫行)尚未提交鎖定的實(shí)體狀態(tài)更改)。
快速失敗
即時(shí)版本增加具有一些有趣的好處:
- 如果版本UPDATE成功(獲取了排他級(jí)鎖),則其他任何并發(fā)事務(wù)都無法修改鎖定的數(shù)據(jù)庫行。 這是將邏輯鎖(版本遞增)升級(jí)為物理鎖(數(shù)據(jù)庫互斥鎖)的時(shí)刻。
- 如果版本UPDATE失敗(因?yàn)槠渌恍┎l(fā)事務(wù)已經(jīng)提交了版本更改),那么我們當(dāng)前正在運(yùn)行的事務(wù)可以立即回滾(而不是在提交期間等待事務(wù)失敗)
后一個(gè)用例可以如下所示:
對(duì)于這種情況,我們將使用以下測試用例:
@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]結(jié)論
像OPTIMISTIC_FORCE_INCREMENT一樣, PESSIMISTIC_FORCE_INCREMENT鎖定模式對(duì)于將實(shí)體狀態(tài)更改傳播到父實(shí)體非常有用。
盡管鎖定機(jī)制相似,但是PESSIMISTIC_FORCE_INCREMENT可以當(dāng)場應(yīng)用,從而允許當(dāng)前運(yùn)行的事務(wù)即時(shí)評(píng)估鎖定結(jié)果。
- 代碼可在GitHub上獲得 。
翻譯自: https://www.javacodegeeks.com/2015/02/hibernate-locking-patterns-pessimistic_force_increment-lock-mode-work.html
總結(jié)
以上是生活随笔為你收集整理的休眠锁定模式– PESSIMISTIC_FORCE_INCREMENT锁定模式如何工作的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 具有Spring Boot和数据功能的J
- 下一篇: 实用程序类与函数式编程无关