孙卫琴的《精通JPA与Hibernate》的读书笔记: 用乐观锁避免并发问题

乐观锁是由应用程序提供的一种机制,这种机制既能保证多个事务并发访问数据,又能防止第二类丢失更新问题。在应用程序中,可以利用Hibernate提供的版本控制功能来实现乐观锁。既可以用一个递增的整数来表示版本号,也可以用时间戳来表示版本号,跟踪数据库表中记录的版本。

下面介绍利用整数类型的版本控制属性对ACCOUNTS表中记录进行版本控制的步骤。

(1)在Account类中定义一个代表版本信息的version属性,这个属性用@Version注解来标识:

@Version
@Column(name="VERSION")
private int version;  

public int getVersion() {
  return this.version;
}

public void setVersion(int version) {
  this.version = version;
}

(2)在ACCOUNTS表中定义一个代表版本信息的字段:

create table ACCOUNTS (
   ID bigint not null,
   NAME varchar(15),
   BALANCE decimal(10,2),
   VERSION integer,
   primary key (ID) 
)  engine=INNODB;

以下transferCheck()实现转账事务,withdraw()方法实现取款事务。这两个方法都采用LockModeType.OPTIMISTIC乐观锁,并且都会处理javax.persistence.OptimisticLockException异常:

  public void transferCheck() throws Exception{
    EntityManager entityManager = 
           entityManagerFactory.createEntityManager();
    EntityTransaction tx = null;
    try {
      tx = entityManager.getTransaction();
      tx.begin();
      log.write("transferCheck():开始事务");
      Thread.sleep(500);

      Account account=entityManager.find(Account.class,
                 Long.valueOf(1),LockModeType.OPTIMISTIC);

      log.write("transferCheck():查询到存款余额为:balance="
                +account.getBalance());
      Thread.sleep(500);
 
      account.setBalance(account.getBalance()+100);
      log.write("transferCheck():汇入100元,把存款余额改为:"
               +account.getBalance());

      tx.commit();
      log.write("transferCheck():提交事务");
      Thread.sleep(500);

    }catch (RuntimeException e) {
      if(e instanceof RollbackException &&
         e.getCause() instanceof OptimisticLockException){

         System.out.println("账户信息已被其他事务修改,本事务被撤销,"
                                +"请重新开始支票转账事务");
         log.write("transfter():账户信息已被其他事务修改,本事务被撤销");
      }  
   
      if (tx != null) {
        tx.rollback();
      }
      throw e;
    } finally {
      entityManager.close();
    }
  }

以上transferCheck()方法加载Account对象时,显式指定使用乐观锁。实际上,只要在Account类中通过@Version注解设置了版本控制属性,那么加载Account对象时,默认情况下会使用乐观锁。因此以下两段代码的作用是等价的:

//显式指定使用乐观锁
Account account=entityManager.find(Account.class,
                 Long.valueOf(1),LockModeType.OPTIMISTIC);

或者:

//当Account类中设置了版本控制属性,默认情况下使用乐观锁
Account account=entityManager.find(Account.class, Long.valueOf(1));                 

接下来介绍加入版本控制后Hibernate的运行时行为。BusinessService类的main()方法先调用registerAccount()方法持久化一个Account对象:

tx = entityManager.getTransaction();
tx.begin(); 
Account account=new Account();
account.setName("Tom");
account.setBalance(1000);
entityManager.persist(account);
tx.commit();

应用程序无需为Account对象的version属性显式赋值,在持久化Account对象时,Hibernate会自动为它赋初始值为0,Hibernate执行的insert语句为:

insert into ACCOUNTS values(1, 'Tom',1000,0);

当Hibernate加载一个Account对象时,它的version属性表示ACCOUNTS表中相关记录的版本。当Hibernate更新一个Account对象时,会根据Session的持久化缓存中Account对象的id与version属性的当前值到ACCOUNTS表中去定位匹配的记录,假定Session缓存中Account对象的version属性为0,那么在取款事务中Hibernate执行的update语句为:

update ACCOUNTS set NAME=’Tom’,BALANCE=900,VERSION=1 
where ID=1 and VERSION=0;

如果存在匹配的记录,就更新这条记录,并且把VERSION字段的值增加为1,此外,还会把Session缓存中Account对象的version属性也更新为1。当支票转账事务接着执行以下update语句时:

update ACCOUNTS set NAME=’Tom’,BALANCE=1100,VERSION=1 
where ID=1 and VERSION=0;

由于ID为1的ACCOUNTS记录的版本已经被取款事务修改,因此找不到匹配的记录,此时Hibernate会抛出StaleObjectStateException,接着JPA把它包装为OptimisticLockException,再把它包装为RollbackException,再把它抛出。
在应用程序中,应该处理该OptimisticLockException异常,这种异常有两种处理方式:

  • 方式一:自动撤销事务,通知用户账户信息已被其他事务修改,需要重新开始事务。本例程就采用这种方式。
  • 方式二:通知用户账户信息已被其他事务修改,显示最新存款余额信息,由用户决定如何继续事务,用户也可以决定立刻撤销事务。

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

孙卫琴书友会

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值