Hibernate Gossip: 乐观锁定(Optimistic locking)

Hibernate Gossip: 乐观锁定(Optimistic locking)[size=xx-small][/size]

悲观锁定假定任何时刻存取数据时,都可能有另一个客户也正在存取同一笔数据,因而对数据采取了数据库层次的锁定状态,在锁定的时间内其它的客户不能对数据进行存取,对于单机或小系统而言,这并不成问题,然而如果是在网络上的系统,同时间会有许多联机,如果每一次读取数据都造成锁定,其后继的存取就必须等待,这将造成效能上的问题,造成后继使用者的长时间等待。

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

在不实行悲观锁定策略的情况下,数据不一致的情况一但发生,有几个解决的方法,一种是先更新为主,一种是后更新的为主,比较复杂的就是检查发生变动的数据来实现,或是检查所有属性来实现乐观锁定。

Hibernate中透过版本号检查来实现后更新为主,这也是Hibernate所推荐的方式,在数据库中加入一个version字段记录,在读取数据时连同版本号一同读取,并在更新数据时比对版本号与数据库中的版本号,如果等于数据库中的版本号则予以更新,并递增版本号,如果小于数据库中的版本号就丢出例外。

实际来透过范例了解Hibernate的乐观锁定如何实现,首先在数据库中新增一个表格:

CREATETABLEuser(

idINT(11)NOTNULLauto_incrementPRIMARYKEY,

versionINT,

nameVARCHAR(100)NOTNULLdefault'',

ageINT

);

这个user表格中的version用来记录版本号,以供Hibernate实现乐观锁定,接着设计User类别,当中必须包括version属性:

User.java

packageonlyfun.caterpillar;

publicclassUser{

privateIntegerid;

privateIntegerversion;//增加版本屬性

privateStringname;

privateIntegerage;

publicUser(){

}

publicIntegergetId(){

returnid;

}

publicvoidsetId(Integerid){

this.id=id;

}

publicIntegergetVersion(){

returnversion;

}

publicvoidsetVersion(Integerversion){

this.version=version;

}

publicStringgetName(){

returnname;

}

publicvoidsetName(Stringname){

this.name=name;

}

publicIntegergetAge(){

returnage;

}

publicvoidsetAge(Integerage){

this.age=age;

}

}

在映射文件的定义方面,则如下所示:

User.hbm.xml

<?xmlversion="1.0"encoding="utf-8"?>

<!DOCTYPEhibernate-mapping

PUBLIC"-//Hibernate/HibernateMappingDTD3.0//EN"

"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">

<hibernate-mapping>

<classname="onlyfun.caterpillar.User"

table="user"

optimistic-lock="version">

<idname="id"column="id"type="java.lang.Integer">

<generatorclass="native"/>

</id>

<versionname="version"

column="version"

type="java.lang.Integer"/>

<propertyname="name"column="name"type="java.lang.String"/>

<propertyname="age"column="age"type="java.lang.Integer"/>

</class>

</hibernate-mapping>

注意<version>标签必须出现在<id>卷标之后,接着您可以试着在数据库中新增数据,例如:

Useruser=newUser();

user.setName("caterpillar");

user.setAge(newInteger(30));

Sessionsession=sessionFactory.openSession();

Transactiontx=session.beginTransaction();

session.save(user);

tx.commit();

session.close();

您可以检视数据库中的数据,每一次对同一笔数据进行更新,version字段的内容都会自动更新,接着来作个实验,直接以范例说明:

//有使用1者开启了一个session1

Sessionsession1=sessionFactory.openSession();

//在这之后,马上有另一个使用者2开启了session2

Sessionsession2=sessionFactory.openSession();

Integerid=newInteger(1);

//使用者1查询数据

UseruserV1=(User)session1.load(User.class,id);

//使用者2查询同一笔数据

UseruserV2=(User)session2.load(User.class,id);

//此时两个版本号是相同的

System.out.println("v1v2"+userV1.getVersion().intValue()+""+userV2.getVersion().intValue());

Transactiontx1=session1.beginTransaction();

Transactiontx2=session2.beginTransaction();

//使用者1更新数据

userV1.setAge(newInteger(31));

tx1.commit();

//此时由于数据更新,数据库中的版本号递增了

//两笔数据版本号不一样了

System.out.println("v1v2"+userV1.getVersion().intValue()+""+userV2.getVersion().intValue());

//userV2的age资料还是旧的

//数据更新

userV2.setName("justin");

//因版本号比数据库中的旧

//送出更新数据会失败,丢出StableObjectStateException例外

tx2.commit();

session1.close();

session2.close();

运行以下的程序片段,会出现以下的结果:

Hibernate:

selectuser0_.idasid0_,user0_.versionasversion0_0_,user0_.nameas

name0_0_,user0_.ageasage0_0_fromuseruser0_whereuser0_.id=?

Hibernate:

selectuser0_.idasid0_,user0_.versionasversion0_0_,user0_.nameas

name0_0_,user0_.ageasage0_0_fromuseruser0_whereuser0_.id=?

v1v200

Hibernate:updateusersetversion=?,name=?,age=?whereid=?andversion=?

v1v210

Hibernate:updateusersetversion=?,name=?,age=?whereid=?andversion=?

16:11:43,187ERRORAbstractFlushingEventListener:277-Couldnotsynchronizedatabasestatewithsession

org.hibernate.StaleObjectStateException:

Rowwasupdatedordeletedbyanothertransaction(orunsaved-value

mappingwasincorrect):[onlyfun.caterpillar.User#1]

atorg.hibernate.persister.entity.BasicEntityPersister.check(BasicEntityPersister.java:1441)

由于新的版本号是1,而userV2的版本号还是0,因此更新失败丢出StableObjectStateException,您可以捕捉这个例外作善后处理,例如在处理中重新读取数据库中的数据,同时将目前的数据与数据库中的数据秀出来,让使用者有机会比对不一致的数据,以决定要变更的部份,或者您可以设计程序自动读取新的数据,并比对真正要更新的数据,这一切可以在背景执行,而不用让您的使用者知道。

要注意的是,由于乐观锁定是使用系统中的程序来控制,而不是使用数据库中的锁定机制,因而如果有人特意自行更新版本讯息来越过检查,则锁定机制就会无效,例如在上例中自行更改userV2的version属性,使之与数据库中的版本号相同的话就不会有错误,像这样版本号被更改,或是由于数据是由外部系统而来,因而版本信息不受控制时,锁定机制将会有问题,设计时必须注意。

相关推荐