Hibernate锁定模式– 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

	@Override
	public 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
	
	@Override
	public 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

	@Override
	public 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>() {
		@Override
		public 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>() {
					@Override
					public Void call() throws Exception {
						return doInTransaction(new TransactionCallable<Void>() {
							@Override
							public 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>() {
		@Override
		public Void execute(Session session) {
			try {
				Repository repository = (Repository) session.get(Repository.class, 1L);

				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.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可以当场应用,从而允许当前正在运行的事务即时评估锁定结果。

翻译自: https://www.javacodegeeks.com/2015/02/hibernate-locking-patterns-pessimistic_force_increment-lock-mode-work.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值