标签:升级 逻辑 结果 策略 -name 金融 就是 param 第一个
首先需要清楚几个基本概念,锁定对于避免因两个并发用户同时更新相同数据而导致的更新冲突至关重要。
使用锁的场景
多台同一服务(分布式环境)/多个事务中可能同时对某一个数据进行操作,增改删等操作时;
一种是代码层次上的,如java中的同步锁,典型的就是同步关键字synchronized,这里我不在做过多的讲解。
另外一种是数据库层次上的,比较典型的就是悲观锁和乐观锁。这里我们重点讲解的就是悲观锁(传统的物理锁)和乐观锁。
正如其名,它指的是对数据被外界(包括本系统当前的其他事务,以及来自外部系统的事务处理)修改持保守态度,因此, 在整个数据处理过程中,将数据处于锁定状态。
悲观锁的实现,往往依靠数据库提供的锁机制(也只有数据库层提供的锁机制才能真正保证数据访问的排他性,否则,即使在本系统中实现了加锁机制,也无法保证外部系 统不会修改数据)。
一个典型的依赖数据库的悲观锁调用:
select * from account where name=”Erica” for update
这条 sql 语句锁定了 account 表中所有符合检索条件( name=”Erica” )的记录。
本次事务提交之前(事务提交时会释放事务过程中的锁),外界无法修改这些记录。
相对悲观锁而言,乐观锁机制采取了更加宽松的加锁机制。悲观锁大多数情况下依靠数据库的锁机制实现,以保证操作最大程度的独占性。但随之而来的就是数据库性能的大量开销,特别是对长事务而言,这样的开销往往无法承受。 如一个金融系统,当某个操作员读取用户的数据,并在读出的用户数据的基础上进行修改时(如更改用户帐户余额),如果采用悲观锁机制,也就意味着整个操作过程中(从操作员读出数据、开始修改直至提交修改结果的全过程,甚至还包括操作员中途去煮咖啡的时间),数据库记录始终处于加锁状态,可以想见,如果面对几百上千个并发,这样的情况将导致怎样的后果。
乐观锁机制在一定程度上解决了这个问题。乐观锁,大多是基于数据版本(Version )记录机制实现。
update xxx,version+1 where xxxx and version=x
何谓数据版本?即为数据增加一个版本标识,在基于数据库表的版本解决方案中,一般是通过为数据库表增加一个 “version” 字段来实现。 读取出数据时,将此版本号一同读出,之后更新时,对此版本号加一。此时,将提交数据的版本数据与数据库表对应记录的当前版本信息进行比对,如果提交的数据版本号大于数据库表当前版本号,则予以更新,否则认为是过期数据。
修改账本的例子
需要注意的是: 乐观锁机制往往基于系统中的数据存储逻辑,因此也具备一定的局限性,如在上例中,由于乐观锁机制是在我们的系统中实现,来自外部系统的用户余额更新操作不受我们系统的控制,因此可能
会造成脏数据被更新到数据库中。在系统设计阶段,我们应该充分考虑到这些情况出现的可能性,并进行相应调整(如将乐观锁策略在数据库存储过程中实现,对外只开放基于此存储过程的数据更新途径,而不是将数据库表直接对外公开)。
JPA在其数据访问引擎中内置了乐观锁实现。如果不用考虑外部系统对数据库的更新操作,利用JPA提供的透明化乐观锁实现,将大大提升我们的 生产力。
JPA为了保证 Update 过程中对象不会被外界修改,会在save方法实现中自动为目标对象加上 WRITE 锁。
好了,说了那么久锁的基本概念以及相关知识,那么我们来看下如何通过JPA实现数据锁的操作。
在JPA中,@Version注解,可以实现乐观锁功能
实体类User,version属性上加@Version注解
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Version;
import java.math.BigDecimal;
@Entity
public class Student{
@Id
private int id;
private String name;
private BigDecimal money;
@Version
private int version;
// getters and setters
}
JPA提供我们两种不同的乐观锁模式(以及两个别名):
OPTIMISTIC (READ)
我们知道OPTIMISTIC以及Read锁,两者是同义词。但JPA规范建议我们在新应用中使用OPTIMISTIC。每当我们请求OPTIMISTIC模式时,总是在事务提交时进行版本检查,这样确保乐观锁可重复读的实现。
简单地说,它确保应该在下面情况下任何事务不能提交:
OPTIMISTIC_INCREMENT (WRITE)
与前面一样,OPTIMISTIC_INCREMENT和WRITE也是同义词,但是前者更可取。 OPTIMISTIC_INCREMENT必须满足与OPTIMISTIC模式相同的条件。然而,它并没有明确规定是否应该立即完成,或者是否应该推迟到提交或刷新。总是累加实体版本(即使此时实体并没有改变) 并在事务提交时进行版本检查,这样确保乐观锁可重复读的实现。
LockModeType.OPTIMISTIC_FORCE_INCREMENT:乐观锁定,默认的锁类型。
LockModeType.OPTIMISTIC_FORCE_INCREMENT:用另一个实体来锁住对当前实体的引用,就需要使用它。提交事务时,即使实体的状态在事务中没有更改,实体的版本列也会递增。
示例:假设我们有两个实体“书(Book)”和“书架(Shelf)”。我们可以将书添加到书架中,但是书不持有对其书架的引用。我们需要对所有移动书到其他书架的动作加锁,以避免一本书被放在2个书架上。为了锁住这个动作,光锁住当前的书架实体是不够的,因为书可能还没有放到某个书架上。锁住所有书架也不合理,因为他们在不同的事务中可能都是不同的。唯一合理的是锁住书实体本身,即使在我们这个例子中它并没有发生变化(因为它并不持有其书架的引用)。
LockModeType.PESSIMISTIC_READ: 悲观锁定。其他事务可以同时读取实体,但不能同时更新它。
LockModeType.PESSIMISTIC_WRITE:悲观锁定。是LockModeType.PESSIMISTIC_READ的增强版。其他事务不能同时读取或写入实体。
注意:OpenJPA 仅针对DB2数据库区分PESSIMISTIC_READ和 PESSIMISTIC_WRITE锁定模式。在与其他数据库一起运行时,这两种模式之间没有区别,因为 PESSIMISTIC_READ锁定模式已升级到PESSIMISTIC_WRITE。
LockModeType.PESSIMISTIC_FORCE_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)
在version中optimistic-lock 属性有如下可选取值:
至此,JPA以及锁的相关概念以及描述完成,如果有描述不清的地方,请指正!
标签:升级 逻辑 结果 策略 -name 金融 就是 param 第一个
原文地址:https://www.cnblogs.com/vana/p/12104085.html