Spring Data Jpa 使用乐观锁 | Java提升营

Spring Data Jpa 使用乐观锁

1.简介

对于企业应用程序,正确的对数据库并发访问至关重要。这意味着我们应该能够以有效且防错的方式处理多个事务。

此外,我们需要确保并发读取和更新之间的数据保持一致。

为此,我们可以使用Java Persistence API提供的乐观锁定机制。这导致在同一时间对同一数据进行的多次更新不会相互干扰。

2.了解乐观锁

为了使用乐观锁,我们需要一个带有 @Version 注解属性的实体。使用它时,每个读取数据的事务都会保留 version 属性的值。

在事务要进行更新之前,它会再次检查 version 属性。

如果与此同时值已被更改,则将引发 OptimisticLockException。否则,事务将提交更新并增加值版本属性。

3.悲观锁与乐观锁

JPA同时提供悲观锁机制,这是处理数据并发访问的另一种机制。

如前所述,乐观锁基于通过检查实体的 version 属性来检测其变化。如果发生任何并发更新,则会发生 OptmisticLockException 异常。之后,我们可以重试更新数据。

乐观锁适合读多写少的应用场景。此外,它在必须分离实体一段时间并且无法持有锁的情况下很有用。

相反,悲观锁机制涉及在数据库级别锁实体。每个事务都可以获取数据锁,只要它持有锁定,任何事务都不能读取,删除或对锁定的数据进行任何更新。

我们可以假设使用悲观锁可能会导致死锁。但是,与乐观锁定相比,它可以确保更高的数据完整性。

4.版本属性

版本属性是带有 @Version 注解的属性。它对于启用乐观锁定是必需的。让我们看一个示例实体类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Entity
public class Student {

@Id
private Long id;

private String name;

private String lastName;

@Version
private Integer version;

// getters and setters

}

声明版本属性时,应遵循几个规则:

  • 每个实体类必须仅有一个版本属性
  • 对于映射到多个表的实体,必须将其放置在主表中
  • 版本属性的类型必须是以下之一:
    1. int
    2. Integer
    3. long
    4. Long
    5. short
    6. Short
    7. java.sql.Timestamp

我们可以通过实体来获取版本属性值,但是我们不能对其进行更新或增加,version 的值由Jpa帮我们来维护。

Jpa可以为没有版本属性的实体支持乐观锁。但是,官方推荐在使用乐观锁时自定义一个版本属性。

5.锁定模式

JPA为我们提供了两种不同的乐观锁定模式(和两种别名):

  • OPTIMISTIC:它为包含版本属性的所有实体获得开放式读取锁定
  • OPTIMISTIC_FORCE_INCREMENT:获得与OPTIMISTIC相同的乐观锁,并额外增加版本属性值
  • READ:它是OPTIMISTIC的同义词
  • WRITE:它是OPTIMISTIC_FORCE_INCREMENT的同义词

我们可以在 LockModeType 类中找到上面列出的所有类型。

5.1.乐观(读)

众所周知,OPTIMISTICREAD 锁模式是同义词。但是,JPA规范建议我们在新的应用程序中使用 OPTIMISTIC

每当我们请求 OPTIMISTIC 锁模式时,Jpa可以皮面数据的脏读和不可重复读。

简而言之,它应确保任何事务都无法提交对另一事务的数据所做的任何修改:

  • 已更新或删除但未提交
  • 同时已成功更新或删除

5.2.OPTIMISTIC_INCREMENT(WRITE)

与前面一样,OPTIMISTIC_INCREMENTWRITE 是同义词,但是前者是更可取的。

OPTIMISTIC_INCREMENT 必须满足与 OPTIMISTIC 锁模式相同的条件。此外,它增加了版本属性的值。

6.使用乐观锁

6.1.Find

要请求乐观锁,我们可以将 LockModeType 作为参数传递给 EntityManager 的 find方法:

1
entityManager.find(Student.class, studentId, LockModeType.OPTIMISTIC);

6.2.Query

启用锁定的另一种方法是使用 Query 对象的 setLockMode 方法:

1
2
3
4
Query query = entityManager.createQuery("from Student where id = :id");
query.setParameter("id", studentId);
query.setLockMode(LockModeType.OPTIMISTIC_INCREMENT);
query.getResultList()

6.3.Explicit Locking

我们可以通过调用 EnitityManagerlock方法来设置锁:

1
2
Student student = entityManager.find(Student.class, id);
entityManager.lock(student, LockModeType.OPTIMISTIC);

6.4.Refresh

我们可以使用与先前方法相同的方式来调用 refresh 方法:

1
2
Student student = entityManager.find(Student.class, id);
entityManager.refresh(student, LockModeType.READ);

####6.5.NamedQuery

@NamedQuerylockMode 属性一起使用:

1
2
3
@NamedQuery(name="optimisticLock",
query="SELECT s FROM Student s WHERE s.id LIKE :id",
lockMode = WRITE)

7. OptimisticLockException

每当发现实体上的乐观锁冲突时,它将引发 OptimisticLockException 异常,我们的服务需要回滚。

我们应该重新加载或刷新实体,之后,我们可以尝试再次更新。

8.结论

文章主要讲了Spring Data Jpa使用乐观锁,它确保任何更新或删除都不会被覆盖或静默丢失。与悲观锁定相反,它不会在数据库级别锁定实体。

给老奴加个鸡腿吧 🍨.