Hibernate Gossip: 乐观锁定(Optimistic locking)

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

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

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

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

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

CREATE TABLE user (
id INT(11) NOT NULL auto_increment PRIMARY KEY,
version INT,
name VARCHAR(100) NOT NULL default '',
age INT
);

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

package onlyfun.caterpillar;

public class User {
private Integer id;
private Integer version; // 增加版本屬性
private String name;
private Integer age;

public User() {
}

public Integer getId() {
return id;
}

public void setId(Integer id) {
this.id = id;
}

public Integer getVersion() {
return version;
}

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

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public Integer getAge() {
return age;
}

public void setAge(Integer age) {
this.age = age;
}
}

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


PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">



table="user"
optimistic-lock="version">





column="version"
type="java.lang.Integer"/>









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

User user = new User();
user.setName( "caterpillar");
user.setAge(new Integer(30));
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
session.save(user);
tx.commit();
session.close();

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

// 有使用1者开启了一个session1
Session session1 = sessionFactory.openSession();
// 在这之后,马上有另一个使用者2开启了session2
Session session2 = sessionFactory.openSession();

Integer id = new Integer(1);

// 使用者1查询数据
User userV1 = (User) session1.load(User.class, id);
// 使用者2查询同一笔数据
User userV2 = (User) session2.load(User.class, id);

// 此时两个版本号是相同的
System.out.println( " v1 v2 "+ userV1.getVersion().intValue() + " " + userV2.getVersion().intValue());

Transaction tx1 = session1.beginTransaction();
Transaction tx2 = session2.beginTransaction();

// 使用者1更新数据
userV1.setAge(new Integer(31));
tx1.commit();

// 此时由于数据更新,数据库中的版本号递增了
// 两笔数据版本号不一样了
System.out.println( " v1 v2 "+ userV1.getVersion().intValue() + " " + userV2.getVersion().intValue());

// userV2 的 age 资料还是旧的
// 数据更新
userV2.setName( "justin");
// 因版本号比数据库中的旧
// 送出更新数据会失败,丢出StableObjectStateException例外
tx2.commit();

session1.close();
session2.close();

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

Hibernate:
select user0_.id as id0_, user0_.version as version0_0_, user0_.name as
name0_0_, user0_.age as age0_0_ from user user0_ where user0_.id=?
Hibernate:
select user0_.id as id0_, user0_.version as version0_0_, user0_.name as
name0_0_, user0_.age as age0_0_ from user user0_ where user0_.id=?
v1 v2 0 0
Hibernate: update user set version=?, name=?, age=? where id=? and version=?
v1 v2 1 0
Hibernate: update user set version=?, name=?, age=? where id=? and version=?
16:11:43,187 ERROR AbstractFlushingEventListener:277 - Could not synchronize database state with session
org.hibernate.StaleObjectStateException:
Row was updated or deleted by another transaction (or unsaved-value
mapping was incorrect): [onlyfun.caterpillar.User#1]
at org.hibernate.persister.entity.BasicEntityPersister.check(BasicEntityPersister.java:1441)

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

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

你可能感兴趣的:(Hibernate)