1.简介
对于企业应用程序,正确的对数据库并发访问至关重要。这意味着我们应该能够以有效且防错的方式处理多个事务。
此外,我们需要确保并发读取和更新之间的数据保持一致。
为此,我们可以使用Java Persistence API提供的乐观锁定机制。这导致在同一时间对同一数据进行的多次更新不会相互干扰。
2.了解乐观锁
为了使用乐观锁,我们需要一个带有 @Version
注解属性的实体。使用它时,每个读取数据的事务都会保留 version
属性的值。
在事务要进行更新之前,它会再次检查 version
属性。
如果与此同时值已被更改,则将引发 OptimisticLockException
。否则,事务将提交更新并增加值版本属性。
3.悲观锁与乐观锁
JPA同时提供悲观锁机制,这是处理数据并发访问的另一种机制。
如前所述,乐观锁基于通过检查实体的 version
属性来检测其变化。如果发生任何并发更新,则会发生 OptmisticLockException
异常。之后,我们可以重试更新数据。
乐观锁适合读多写少的应用场景。此外,它在必须分离实体一段时间并且无法持有锁的情况下很有用。
相反,悲观锁机制涉及在数据库级别锁实体。每个事务都可以获取数据锁,只要它持有锁定,任何事务都不能读取,删除或对锁定的数据进行任何更新。
我们可以假设使用悲观锁可能会导致死锁。但是,与乐观锁定相比,它可以确保更高的数据完整性。
4.版本属性
版本属性是带有 @Version
注解的属性。它对于启用乐观锁定是必需的。让我们看一个示例实体类:
1 |
|
声明版本属性时,应遵循几个规则:
- 每个实体类必须仅有一个版本属性
- 对于映射到多个表的实体,必须将其放置在主表中
- 版本属性的类型必须是以下之一:
- int
- Integer
- long
- Long
- short
- Short
- java.sql.Timestamp
我们可以通过实体来获取版本属性值,但是我们不能对其进行更新或增加,version
的值由Jpa帮我们来维护。
Jpa可以为没有版本属性的实体支持乐观锁。但是,官方推荐在使用乐观锁时自定义一个版本属性。
5.锁定模式
JPA为我们提供了两种不同的乐观锁定模式(和两种别名):
- OPTIMISTIC:它为包含版本属性的所有实体获得开放式读取锁定
- OPTIMISTIC_FORCE_INCREMENT:获得与OPTIMISTIC相同的乐观锁,并额外增加版本属性值
- READ:它是OPTIMISTIC的同义词
- WRITE:它是OPTIMISTIC_FORCE_INCREMENT的同义词
我们可以在 LockModeType
类中找到上面列出的所有类型。
5.1.乐观(读)
众所周知,OPTIMISTIC
和 READ
锁模式是同义词。但是,JPA规范建议我们在新的应用程序中使用 OPTIMISTIC
。
每当我们请求 OPTIMISTIC
锁模式时,Jpa可以皮面数据的脏读和不可重复读。
简而言之,它应确保任何事务都无法提交对另一事务的数据所做的任何修改:
- 已更新或删除但未提交
- 同时已成功更新或删除
5.2.OPTIMISTIC_INCREMENT(WRITE)
与前面一样,OPTIMISTIC_INCREMENT
和 WRITE
是同义词,但是前者是更可取的。
OPTIMISTIC_INCREMENT
必须满足与 OPTIMISTIC
锁模式相同的条件。此外,它增加了版本属性的值。
6.使用乐观锁
6.1.Find
要请求乐观锁,我们可以将 LockModeType
作为参数传递给 EntityManager
的 find方法:
1 | entityManager.find(Student.class, studentId, LockModeType.OPTIMISTIC); |
6.2.Query
启用锁定的另一种方法是使用 Query
对象的 setLockMode
方法:
1 | Query query = entityManager.createQuery("from Student where id = :id"); |
6.3.Explicit Locking
我们可以通过调用 EnitityManager
的lock方法来设置锁:
1 | Student student = entityManager.find(Student.class, id); |
6.4.Refresh
我们可以使用与先前方法相同的方式来调用 refresh
方法:
1 | Student student = entityManager.find(Student.class, id); |
####6.5.NamedQuery
将 @NamedQuery
与 lockMode
属性一起使用:
1 |
7. OptimisticLockException
每当发现实体上的乐观锁冲突时,它将引发 OptimisticLockException
异常,我们的服务需要回滚。
我们应该重新加载或刷新实体,之后,我们可以尝试再次更新。
8.结论
文章主要讲了Spring Data Jpa使用乐观锁,它确保任何更新或删除都不会被覆盖或静默丢失。与悲观锁定相反,它不会在数据库级别锁定实体。