JPA 乐观锁教程

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/neweastsun/article/details/82318734

JPA 乐观锁

在企业级应用中,管理对数据库资源的并发访问至关重要。这意味着我们应该能够以一种有效且最重要防错方式处理多个事务。
更重要的是,我们需要确保数据在并发读取和更新时的一致性。为此,我们可以使用Java Persistence API提供的乐观锁机制。其实现同一时间对同一数据进行多次更新不会相互干扰。

理解乐观锁

乐观锁是不是数据库提供的机制。这里通过在实体中增加一个带有@Version注解的属性实现乐观锁。在实际使用过程中每个事务读取该属性的值,事务更新数据之前,再次检查该属性的值。如果该值此时已经改变则抛出OptimisticLockException 异常,否则事务提交更新并版本属性加一。

悲观锁 VS 乐观锁

与乐观锁相对应,JPA也提供了另一种处理并发访问机制——悲观锁。下面简要说明两种直接差异以及各种优势。

如前面示例所示,乐观锁是基于检测实体中版本属性变化机制。如果并发更新发生,抛出OptimisticLockException 异常,然后我们可以重新尝试跟新数据。

可以想象这种机制比较适合应用中读操作远多于更新或删除操作。而且在实体必须分离一段时间且不能持有锁的情形下很有用。

相反,悲观锁机制是在数据库级锁定实体。每个事务可以获得数据锁,只要其持有锁,其他事务不能对该数据进行读操作,删除或任何更新操作。我们可以推断使用悲观锁定可能导致死锁。但是确保了比乐观锁定更好的数据完整性。

版本属性

版本属性是带有 @Version注解的属性。对启用乐观锁这是必须,请看示例:

@Entity
public class Student {

    @Id
    private Long id;

    private String name;

    private String lastName;

    @Version
    private Integer version;

    // getters and setters

}

申明版本属性时需遵守几个规则:

  • 每个实体必须只能有一个版本属性
  • 当一个实体映射到多个表时,版本属性必须在主表中
  • 版本属性的类型必须是这几种类型之一:int, Integer, long, Long, short, Short, java.sql.Timestamp

我们知道我们可以通过实体获取版本属性的值,但为确保数据一致性,我们不能更新或增加它,仅持久化提供者可以。

值得注意的是,持久性提供者能够支持没有版本属性的实体的乐观锁。但在使用乐观锁定,最好总是包含版本属性。如果我们试图锁定一个不包含该属性的实体,而持久性提供程序又不支持它,则会导致一个PersitenceException。

锁模式

JPA提供我们两种不同的乐观锁模式(以及两个别名):

  • OPTIMISTIC – 所有包含版本属性的实体获得乐果读锁
  • OPTIMISTIC_FORCE_INCREMENT – 与OPTIMISTIC一样获得锁并自动增加版本属性的值
  • READ – OPTIMISTIC 的同义词
  • WRITE – OPTIMISTIC_FORCE_INCREMENT的同义词
    上述所有类型在LockModeType 中有完整定义.

OPTIMISTIC (READ)

我们知道OPTIMISTIC以及Read锁,两者是同义词。但JPA规范建议我们在新应用中使用OPTIMISTIC。每当我们请求OPTIMISTIC模式时,持久性提供程序将阻止数据进行脏读和不可重复读。
简单地说,它确保应该在下面情况下任何事务不能提交:
* 已经更新或删除但未提交
* 已经同时成功地更新或删除

OPTIMISTIC_INCREMENT (WRITE)

与前面一样,OPTIMISTIC_INCREMENT和WRITE也是同义词,但是前者更可取。
OPTIMISTIC_INCREMENT必须满足与,OPTIMISTIC模式相同的条件。此外,它增加了version属性的值。然而,它并没有明确规定是否应该立即完成,或者是否应该推迟到提交或刷新。

需要注意的是当请求OPTIMISTIC模式时,持久性提供者是否提供OPTIMISTIC_INCREMENT功能。

使用乐观锁

对拥有版本属性实体,缺省情况下乐观锁是有效的。但还有其他几种方式显示指定:

Find

通过给EntityManager的find方法传递适当的LockModeType作为参数,可以获取乐观锁:

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

Query

另一种方法是通过Query对象的SetLockMode方法:

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

显示指定锁

通过调用EntityManager的lock方法显示指定锁:

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

Refresh

与之前一样也可以调用refresh方法实现:

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

NamedQuery

最后一个使用@NamedQuery注解的lockMode属性:

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

OptimisticLockException

当JAP实现(持久化提供者)发现在实体上乐观锁冲突时,抛出OptimisticLockException异常。我们指定因为异常导致活动事务总是回滚。

如何对OptimisticLockException做出反应是有必要的。为了方便此异常包含对冲突实体的引用。然而,JAP实现(持久性提供者)并不是在每种情况下都必须提供它。无法保证对象是否可用。
因此,建议使用一种更好的方法来处理该异常。我们应该通过最好在新的事务中重新加载或刷新来重新检索实体。然后尝试再次更新。

总结

在本问中我们学习可以帮助处理并发事务的乐观锁工具,使用实体中包含的版本属性来控制对它们的并发修改。因此,它确保任何更新或删除不会被无意地覆盖或丢失。与悲观锁定相反,它不会在数据库级别上锁定实体,因此不会引起DB级死锁。

对拥有版本属性的实体默认启用了乐观锁。但也可以通过使用各种锁模式类型显式地指定乐观锁的其他几种方法。
我们应该记住当实体上有冲突更新时,会出现OptimisticLockException异常。

猜你喜欢

转载自blog.csdn.net/neweastsun/article/details/82318734
JPA