现在的位置: 首页 > 综合 > 正文

Hibernate乐观锁与悲观锁

2018年02月04日 ⁄ 综合 ⁄ 共 3941字 ⁄ 字号 评论关闭

1、Pessimistic Locking 悲观锁:

它指的是对数据被外界修改持保守态度。假定任何时刻存取数据时,都可能有另一个客户也正在存取同一笔数据,为了保持数据被操作的一致性,于是对数据采取了数据库层次的锁定状态,依靠数据库提供的锁机制来实现。

基于jdbc实现的数据库加锁如下:

select * from account where name="mary" for update

在更新的过程中,数据库处于加锁状态,任何其他的针对本条数据的操作都将被延迟。本次事务提交后解锁

Hibernate 的加锁模式(LockMode类指定)有:

LockMode.NONE

 

如果缓存中存在对象,直接返回该对象的引用,否则通过select语句到数据库中加载该对象,默认值.

LockMode.READ

 

不管缓存中是否存在对象,总是通过select语句到数据库中加载该对象,如果映射文件中设置了版本元素,就执行版本检查,比较缓存中的对象是否和数据库中对象版本一致

LockMode.UPGRADE

 

不管缓存中是否存在对象,总是通过select语句到数据库中加载该对象,如果映射文件中设置了版本元素,就执行版本检查,比较缓存中的对象是否和数据库中对象的版本一致,如果数据库系统支持悲观锁(如Oracle/MySQL),就执行select...forupdate语句,如果不支持(如Sybase),执行普通select语句

LockMode.UPGRADE_NOWAIT

 

和LockMode.UPGRADE具有同样功能,此外,对于Oracle等支持updatenowait的数据库,执行select...for updatenowait语句,nowait表明如果执行该select语句的事务不能立即获得悲观锁,那么不会等待其它事务释放锁,而是立刻抛出锁定异常

LockMode.WRITE

 

保存对象时会自动使用这种锁定模式,仅供Hibernate内部使用,应用程序中不应该使用它

LockMode.FORCE

 

强制更新数据库中对象的版本属性,从而表明当前事务已经更新了这个对象

(此处加锁模式参考网友文章)http://blog.sina.com.cn/s/blog_6ac4c6cb010186cn.html

显式的用户指定"可以通过以下几种方式之一来表示:

•调用 Session.load() 的时候指定锁定模式(LockMode)。

•调用 Session.lock()

•调用 Query.setLockMode()

如果在 UPGRADE 或者 UPGRADE_NOWAIT 锁定模式下调用 Session.load(),并且要读取的对象尚未被session 载入过,那么对象通过 SELECT ... FOR UPDATE 这样的 SQL 语句被载入。如果为一个对象调用 load() 方法时,该对象已经在另一个较少限制的锁定模式下被载入了,那么 Hibernate 就对该对象调用 lock() 方法。

如果指定的锁定模式是 READ,UPGRADE 或 UPGRADE_NOWAIT,那么 Session.lock() 就执行版本号检查。(在 UPGRADE 或者 UPGRADE_NOWAIT 锁定模式下,执行 SELECT ... FOR UPDATE这样的SQL语句。)

如果数据库不支持用户设置的锁定模式,Hibernate 将使用适当的替代模式(而不是扔出异常)。这一点可以确保应用程序的可移植性。

例:利用悲观锁协调并发执行的取款事务和支票转账事务

Account account=(Account)session.get

(Account.class,new Long(1), LockMode.UPGRADE);

account.setBalance(account.getBalance()-100);

Hibernate执行的select语句为:

select * from ACCOUNTS where ID=1 for update;

update ACCOUNTS set BALANCE=900…


2.Optimistic   Locking 乐观锁:

乐观锁定(optimistic locking)则乐观的认为资料的存取很少发生同时存取的问题,因而不作数据库层次上的锁定,为了维护正确的数据,乐观锁定采用应用程序上的逻辑实现版本控制的方法。

•乐观锁是由应用程序提供的一种机制,这种机制既能保证多个事务并发访问数据,又能防止第二类丢失更新问题。

•在应用程序中,可以利用Hibernate提供的版本控制功能来实现乐观锁。对象-关系映射文件中的<version>元素和<timestamp>元素都具有版本控制功能:

–<version>元素利用一个递增的整数来跟踪数据库表中记录的版本

–<timestamp>元素用时间戳来跟踪数据库表中记录的版本。


例:使用<version>元素实现对ACCOUNTS表中的记录进行版本控制:

•(1)在Account类中定义一个代表版本信息的属性:
private int version;
public int getVersion() {
return this.version;
}
public void setVersion(int version) {
this.version = version;
}

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

•(3)在Account.hbm.xml文件中用<version>元素来建立Account类的version属性与ACCOUNTS表中VERSION字段的映射:

<id name="id" type="long" column="ID">

<generator class="increment"/>

</id>

<version name="version" column="VERSION" />

……

•当Hibernate更新一个Account对象时,会根据它的id与version属性到ACCOUNTS表中去定位匹配的记录,假定Account对象的version属性为0,那么在取款事务中Hibernate执行的update语句为:

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

•如果存在匹配的记录,就更新这条记录,并且把VERSION字段的值加1。当支票转账事务接着执行以下update语句时:

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

•由于ID为1的ACCOUNTS记录的版本已经被取款事务修改,因此找不到匹配的记录,此时Hibernate会抛出StaleObjectStateException

利用乐观锁协调并发执行的取款事务和支票转账事务:

•在应用程序中应该捕获该异常,这种异常有两种处理方式:

–方式一:自动撤销事务,通知用户账户信息已被其他事务修改,需要重新开始事务。

–方式二:通知用户账户信息已被其他事务修改,显示最新存款余额信息,由用户决定如何继续事务,用户也可以决定立刻撤销事务。

try {
tx = session.beginTransaction();
log.write("transferCheck():开始事务");
Thread.sleep(500);
Account account=(Account)session.get(Account.class,new Long(1));
log.write("transferCheck():查询到存款余额为:balance="+account.getBalance());
Thread.sleep(500);
account.setBalance(account.getBalance()+100);
log.write("transferCheck():汇入100元,把存款余额改为:"+account.getBalance());
tx.commit(); //当Hibernate执行update语句时,可能会抛出StaleObjectException
log.write("transferCheck():提交事务");
Thread.sleep(500);
}catch(StaleObjectStateException e){
if (tx != null) {
tx.rollback();
}
e.printStackTrace();
System.out.println("账户信息已被其他事务修改,本事务被撤销,请重新开始支票转账事务");
log.write("transferCheck():账户信息已被其他事务修改,本事务被撤销");
}

实现乐观锁的其它方法:

•如果应用程序是基于已有的数据库(如遗留项目),而数据库表中不包含代表版本或时间戳的字段,Hibernate提供了其他实现乐观锁的办法。把<class>元素的optimistic-lock属性设为“all”:

<class name="Account" table="ACCOUNTS" optimistic-lock="all" dynamic-update="true">

•Hibernate会在update语句的where子句中包含Account对象被加载时的所有属性:

update ACCOUNTS set BALANCE=900 where ID=1 and NAME='Tom' and BALANCE='1000';

(参考风中叶张龙老师教学课件)

转载请注明出处:http://blog.csdn.net/jialinqiang/article/details/8723051

抱歉!评论已关闭.