JPA的锁机制

JPA 各类实体锁模式的区别

字数2084 阅读304 评论0 

为了可以同步访问实体,JPA提供了2种锁机制。这两种机制均可以免两个事务中的其中一个,在不知情的状况下覆盖另外一个事务的数据。html

经过实体锁,咱们一般但愿避免在两个并行事务中产生以下情形:java

  1. Adam的事务读取数据 X
  2. Barbara的事务读取数据 X
  3. Adam的事务修改数据 X,并将其修改成 XA
  4. Adam的事务写入数据 XA
  5. Barbara的事务修改数据 X,并将其修改成 XB
  6. Barbara的事务写入数据 XB

结果是,Adam所作的修改彻底被Barbara所覆盖掉了,可是Barbara对此却绝不知晓。像这样的状况一般被称为“脏读”。显然,咱们但愿的结果是Adam写入 XA,而Barbara须要在写入 XB以前检查对 XA 的修改。数据库

乐观锁的工做原理

乐观锁基于的假设是实际中冲突不多发生,即便发生,抛出一个错误也比想办法避免它们更容易接受和简单。在乐观锁中,容许一个事务正确完成,但另外一个事务须要抛出异常并回滚,而且必须被从新执行或者丢弃。apache

咱们还以Adam和Barbara为例,下面是一个使用乐观锁可能发生的情形:api

  1. Adam的事务读取数据 X
  2. Barbara的事务读取数据 X
  3. Adam的事务修改数据 X,并将其修改成 XA
  4. Adam的事务写入数据 XA
  5. Barbara的事务修改数据 X,并将其修改成 XB
  6. Barbara的事务试图写入数据 XB,可是收到一个错误
  7. Barbara须要读取数据 XA(或者从新开始一个新的事务)
  8. Barbara的事务修改数据 XA,并将其修改成 XAB
  9. Barbara的事务写入数据 XAB

如你所见,Barbara被强制要求检查Adam的修改,而且她能够选择继续修改Adam的结果并保存(合并修改)。最后的数据将同时包括Adam和Barbara的修改。oracle

乐观锁彻底由JPA控制。它须要在DB表中额外存储一个版本号列。它彻底依靠于底层用来存储关系型数据的DB引擎来工做。ui

悲观锁的工做原理

对于某些人来讲,悲观锁更容易接受。当事务须要修改一个可能被其余事务同时修改的实体时,事务会发起一个命令将实体锁住。全部的锁会持续到事务结束后再自动释放。spa

使用悲观锁的情形可能以下所示:设计

  1. Adam的事务读取数据 X
  2. Adam的事务锁住 X
  3. Barbara的事务但愿读取数据 X,可是由于 X 已经被锁住,只好等待
  4. Adam的事务修改数据 X,并将其修改成 XA
  5. Adam的事务写入数据 XA
  6. Barbara的事务读取数据 XA
  7. Barbara的事务修改数据 XA,并将其修改成 XAB
  8. Barbara的事务写入数据 XAB

如你所见,Barbara又一次被强制的写入 XAB,同时也包含了Adam的修改。可是,这个方案与乐观锁彻底不一样——Barbara须要等待Adam的事务完成之后才可以读取数据。更甚的是,为了让该场景正确工做,咱们须要在两个事务中都手动发起一个lock命令。(由于咱们并不肯定那个事务先运行,因此两个事务都须要在修改数据前先进行锁定)虽然乐观锁要为每一个实体增长一个版本列,比悲观锁工做略多,可是以后咱们不须要再在事务中发起锁操做了。JPA会自动完成全部的检查,咱们只须要处理可能的异常便可。code

悲观锁使用底层数据库提供的锁机制来锁住表中已有的记录。JPA须要知道如何触发这些锁,而且尚不能彻底支持某些数据库。

即便是JPA规范中也说到,不须要提供PESSIMISTIC_READ(由于许多DB只支持WRITE锁):

容许JPA实现用LockModeType.PESSIMISTIC_WRITE来代替LockModeType.PESSIMISTIC_READ,可是反之不可。

JPA中可用的锁类型

首先,我想说,对于实体中有添加了@Version注解的列,JPA会自动对该实体使用乐观锁。你不须要使用任何锁命令。可是,你能够在任什么时候候发起一个如下类型的锁:

  1. LockModeType.Optimistic
    1. 这就是默认的锁类型。也是如ObjectDB所说一般被你们所忽略的锁类型。在个人印象中,只有在须要动态获取并传递锁类型时,才会用到它,即便咱们很清楚最后的锁是OPTIMISTIC的。虽然这个例子不太恰当,可是一个好的API设计,即便是默认值也应该为其提供一个可选项。
    2. 示例:Java
LockModeType lockMode = resolveLockMode();
A a = em.find(A.class, 1, lockMode);
  1. LockModeType.OPTIMISTIC_FORCE_INCREMENT

    1. 这个选项不多被用到。可是若是你但愿用另外一个实体来锁住对当前实体的引用,就须要使用它。换句话说,即便当前实体没有被修改,可是其余实体可能由于当前实体被修改,你就能够用它来锁住对当前实体的引用。
    2. 示例:
      1. 假设咱们有两个实体“书(Book)”和“书架(Shelf)”。咱们能够将书添加到书架中,可是书不持有对其书架的引用。咱们须要对全部移动书到其余书架的动做加锁,以免一本书被放在2个书架上。为了锁住这个动做,光锁住当前的书架实体是不够的,由于书可能尚未放到某个书架上。锁住全部书架也不合理,由于他们在不一样的事务中可能都是不一样的。惟一合理的是锁住书实体自己,即便在咱们这个例子中它并无发生变化(由于它并不持有其书架的引用)。
  2. LockModeType.PESSIMISTIC_READ

    1. 这个模式相似于LockModeType.PESSIMISTIC_WRITE,可是有一点不一样:若是没有事务对实体加写锁,那么就不能阻塞对该实体的读取。它还容许其余事务使用LockModeType.PESSIMISTIC_READ来加锁。WRITE锁和READ锁之间的区别,已经被这两篇文章(here (ObjectDB) 和 here (OpenJPA))很详细的说明了。可是,不只由于规范中容许,并且许多实现也没有分开处理,因此该锁模式常常被等价于LockModeType.PESSIMISTIC_WRITE
  3. LockModeType.PESSIMISTIC_WRITE

    1. 这是LockModeType.PESSIMISTIC_READ的加强版。当WRITE锁发生时,JPA在数据库的帮助下,会阻止其余事务读取实体,而不像READ锁那样只禁止写入。
  4. LockModeType.PESSIMISTIC_FORCE_INCREMENT

    1. 这是另外一个不多使用的锁模式。可是,它能够用来结合PESSIMISTICOPTIMISTIC时使用。在如下场景中,单独使用PESSIMISTIC_WRITE是无效的:

      1. 事务A使用乐观锁并读取实体E
      2. 事务B请求实体E上的WRITE锁
      3. 事务B提交并释放E上的锁
      4. 事务A更新E并提交
    2. 在步骤4中,若是事务B没有增长版本列的值,那么就没法阻止事务A覆盖B的修改。即便事务B使用的是悲观锁,锁模式LockModeType.PESSIMISTIC_FORCE_INCREMENT也会强制事务B更新版本号,并让事务A失败并抛出OptimisticLockException

为了发起一个指定类型的锁,JPA提供了如下方法:

你可使用JPA中这两种锁机制中的任意一种。若是须要,也能够选择悲观锁类型PESSIMISTIC_FORCE_INCREMENT,把两者混起来用。

转自:http://www.jianshu.com/p/4bc01d3c980a

相关文章
相关标签/搜索