Locking and Concurrency in Java Persistence 2.0(Java Persistence 2.0的锁和并发控制)

JPA提供了基于POJO的持久化模型,主要处理数据跟Java对象间的映射,而且也标准化了这种映射。JPA2.0规范中增加了两个主要的特性:OR映射的查询功能及锁的支持。

 

锁机制跟并发

 

锁是一种在技术上实现数据库事务的并发控制,当两个或多个数事务并发访问同一个数据时,锁就可以保证在同一时刻只有一个事务可以访问修改此数据。

 

有两种类型的锁:乐观锁跟悲观锁。乐观锁就是在多个并发事务之间并不会引起冲突,就是说并不会有两个事务同时去读和写同一个数据。所以这种锁会提供很大的自由去操作java对象,但是如果有两个事务同时操作一个数据时就会产生冲突,故我们要有一种检查来避免这种冲突。

 

悲观锁就是在事务间会经常发生冲突,这种锁是在读取数据的时候就已经把数据锁住,其他事务只能等这个数据释放掉锁之后才能访问。

 

当没有事务间的冲突时乐观锁是最佳选择,反之则悲观锁是最佳选择。

 

使用JPA可以在实体上使用锁,这样你就可以控制住何时,何地使用什么类型的锁。一个实体就是一个轻量级的持久化域对象,一个实体对应一个数据库表,而一个实体的实例就代表表中的一条记录。

 

JPA1.0规范锁的支持

 

JPA1.0只支持乐观读跟乐观写两种类型的锁,任何事物都可以读写实体对象。当一个事务提交时,JPA会检查实体的属性值Version的值是否被修改过,如果这个值已经被修改过,那么JPA会抛出例外。好处就是数据库不需要任何锁,这样要比悲观锁更灵活,但缺点是用户或者应用程序一旦失败后必须刷新。

 

在实体上标注@Version即可:

 

public class User {
       @ID int id;
       @Version int version;

 

对应的数据库sql:

 

 CREATE TABLE USER
   (ID NUMBER NOT NULL, VERSION NUMBER),
      PRIMARY KEY (ID));

 

属性 version 的类型可以是:int、short、long或者是timesamp。

翻译成sql:

 

UPDATE User SET ..., version = version + 1
     WHERE id = ? AND version = readVersion

 

 附图img0.jpg很详细的解释了乐观锁的机制。

当两个并发事务同时修改对象Part p时,事务1首先提交,p属性version的值会自动加1。当事务2提交时就会抛出例外OptimisticLockException。因为事务2里p对象的version的值要小于当前数据库里version的值,所以事务2就会回滚事务。

 

你还可以通过指定锁的模式来控制实体。EntityManager 类里的方法logc().就可以实现:

 

public void lock(Object entity, LockModeType lockMode);

 

参数1是实体对象,参数2是锁模式。

 

JPA1.0支持的模式:

 

READ:事务中提交之前给实体加锁,比较当前实体的version值与数据库中的值是否一致,如果不一致即数据库中version的值大于当前实体的version的值,那么就抛出例外:OptimisticLockException 并回滚事务。

 

WRITE:操作同READ,唯一不同是无论事务是否成功提交都要将version值加1.

 

JPA2.0中事务类型:

 

乐观锁:

    OPTIMISTIC:同READ

    OPTIMISTIC_FORCE_INCREMENT:同WRITE

 

悲观锁:

   PESSIMISTIC_READ:当一个事务读取一个实体时就给其加锁,直到这个事务完成才将锁释放。通常在不允许重复读的情况下使用该锁。换句话说就是在读时不允许修改此记录。在此锁模式下可以运行其他事务读记录。

  PESSIMISTIC_WRITE:在修改一个实体对象时加锁,当多个事务试图修改记录时会强制序列化,通常在并发修改失败 时会用到该锁模式。

  PESSIMISTIC_FORCE_INCREMENT :当事务读取实体时就给其加锁,当事务结束时都要将version值加1.

 

JPA2.0也提供了多种方式给一个实体加锁。你可以在lock()  跟 find() 方法里指定锁模式。

此外如果你调用EntityManager.refresh()的方法,就会同步当前实体对象跟数据库对象的状态。

 

你也可以使用setLockMode()方法给查询设置锁。

 

在JPA2.0里使用锁的例子。

OPTIMISTIC 锁的典型应用是一个实体直接引用到其他的实体对象。

如附件img1.jpg所示的,事务1更改了part对象p1的价格,同时把p1的version的值加1.事务2提交一个User对象u1的bid对象,如果对象part的price值小于当前user对象bid的值,那么事务2就增加bid的值。

 

在这种情况下,当事务2读取了price值之后,被事务1修改了,那么我们是不希望事务2提交的。这种情况下OPTIMISTIC 是最好的选择。

 

em.lock(p1, OPTIMISTIC);

 

然后事务2调用em.flush();这会增加p1对象的version值,任何并发访问p1的事务都会失败,抛出 OptimisticLockException 例外且回滚事务。如图示,当事务2执行em.flush()后,事务1尝试去修改p1对象的price值,当事务1提交时,会检查version的值,因为version的值已经被事务2修改,所以事务1在提交事务时会抛出OptimisticLockException 例外。

 

悲观锁模式:

 

当数据库的一行记录被读取后就会将此记录加锁,就相当于执行sql:SELECT . . . FOR UPDATE [NOWAIT] .悲观锁能够保证事务不会再同一时刻修改同一记录,这可以简化代码,但是却限制了数据的读取,很可能会引起死锁的问题。如果并发发生情况很多时使用悲观锁是比较好的选择。

 

附件img3.jpg是 PESSIMISTIC_READ锁的示例。

附件img4.jpg是 PESSIMISTIC_WRITE锁的示例。

附件img5.jpg是 PESSIMISTIC_WRITE锁的示例。

你可能感兴趣的:(java,sql,jpa)