本文是Hibernate和JPA中基于版本的乐观并发控制的简介。 这个概念已经很老了,上面已经写了很多东西,但是无论如何我都看到了它被重新发明,误解和滥用。 我在编写它只是为了传播知识,并希望引起人们对并发控制和锁定的兴趣。
用例
假设我们有一个供多个用户使用的系统,其中每个实体可以由多个用户修改。 我们希望避免两个人加载一些信息,根据他们看到的内容做出一些决定并同时更新状态的情况。 我们不希望丢失在第一个交易中首先单击“保存”的用户通过覆盖它们所做的更改。
它也可能在服务器环境中发生–多个事务可以修改共享实体,我们希望避免出现以下情况:
- 交易1载入资料
- 事务2更新该数据并提交
- 使用在步骤1中加载的状态(不再是当前状态),事务1执行一些计算并更新状态
在某些方面,它与不可重复的读取具有可比性。
解决方案:版本控制
因此,Hibernate和JPA实现了基于版本的并发控制的概念。 运作方式如下。
您可以使用@Version
或<version>
(数字或时间戳)标记一个简单的属性。 这将是数据库中的特殊列。 我们的映射如下所示:
@Entity
@Table(name = 'orders')
public class Order {@Idprivate long id;@Versionprivate int version;private String description;private String status;// ... mutators
}
当这样的实体持续存在时,version属性将设置为起始值。
每当更新时,Hibernate都会执行如下查询:
update orders
set description=?, status=?, version=?
where id=? and version=?
请注意,在最后一行, WHERE
子句现在包括version
。 此值始终设置为“旧”值,因此只有在具有预期版本的情况下,它才会更新行。
假设有两个用户在版本1中加载订单,并花一些时间在GUI中查看订单。
安妮决定批准该订单并执行该操作。 数据库中的状态已更新,一切正常。 传递给update语句的版本如下:
update orders
set description=?, status=?, version=2
where id=? and version=1
如您所见,在持久化更新持久层时,版本计数器将增加到2。
在她的GUI中,Betty仍然具有旧版本(编号1)。 当她决定对订单执行更新时,该语句如下所示:
update orders
set description=?, status=?, version=2
where id=? and version=1
此时,在Anne的更新之后,数据库中该行的版本为2。因此,第二次更新影响0行(没有与WHERE
子句匹配的行)。 Hibernate会检测到它,并检测到一个org.hibernate.StaleObjectStateException
(包装在javax.persistence.OptimisticLockException
)。
结果,第二个用户除非刷新视图,否则无法执行任何更新。 为了获得适当的用户体验,我们需要进行一些干净的异常处理,但是我将省略。
组态
这里几乎没有要自定义的内容。 @Version
属性可以是数字或时间戳。 数字是人为的,但通常在内存和数据库中占用较少的字节。 时间戳较大,但始终会更新为“当前时间戳”,因此您可以实际使用它来确定实体的更新时间。
为什么?
那为什么要使用它呢?
- 它提供了一种方便且自动化的方式来维持上述情况下的一致性。 这意味着每个动作只能执行一次,并且可以确保用户或服务器进程在制定业务决策时看到最新状态。
- 设置只需很少的工作。
- 由于其乐观的性质,因此速度很快。 在任何地方都没有锁定,只有一个字段添加到同一查询中。
- 在某种程度上,即使在已提交读事务隔离级别的情况下,它也可以确保可重复读。 它将以一个异常结束,但是至少不可能创建不一致的状态。
- 它适用于非常长的对话,包括跨越多个事务的对话。
- 在ACID数据库上的所有可能情况和竞争条件下,它都是完全一致的。 更新必须是顺序更新,更新涉及行锁定,而“第二”更新将始终影响0行并失败。
演示版
为了演示这一点,我创建了一个非常简单的Web应用程序。 它将Spring和Hibernate连接在一起(在JPA API后面),但是它也可以在其他设置中工作:Pure Hibernate(无JPA),具有不同实现的JPA,非webapp,非Spring等。
该应用程序保留一个具有与上述类似的架构的Order
,并以Web表单显示该Order
,您可以在其中更新描述和状态。 要尝试并发控制,请在两个选项卡中打开页面,进行不同的修改并保存。 不使用@Version
尝试相同的@Version
。
它使用嵌入式数据库,因此需要最少的设置(仅Web容器),并且只需重新启动即可从新数据库开始。
这非常简单-在@Transactional
@Controller
访问EntityManager
并直接使用JPA映射的实体支持表单。 对于不太琐碎的项目而言,这可能不是最好的处理方法,但是至少它将所有代码集中在一个地方并且非常容易掌握。
可以在我的GitHub存储库中找到Eclipse项目的完整源代码。
参考: 在我们的JCG合作伙伴 Konrad Garus的Squirrel博客上,JPA / Hibernate中基于版本的乐观并发控制 。
翻译自: https://www.javacodegeeks.com/2012/11/jpahibernate-version-based-optimistic-concurrency-control.html