悲觀鎖定假定任何時(shí)刻存取資料時(shí),都可能有另一個(gè)客戶(hù)也正在存取同一筆資料,因而對(duì)資料採(cǎi)取了資料庫(kù)層次的鎖定狀態(tài),在鎖定的時(shí)間內(nèi)其他的客戶(hù)不能對(duì)資料進(jìn)行存取,對(duì)於單機(jī)或小系統(tǒng)而言,這並不成問(wèn)題,然而如果是在網(wǎng)路上的系統(tǒng),同時(shí)間會(huì)有許多連線,如果每一次讀取資料都造成鎖定,其後繼的存取就必須等待,這將造成效能上的問(wèn)題,造成後繼使用者的長(zhǎng)時(shí)間等待。
樂(lè)觀鎖定(Optimistic locking)則樂(lè)觀的認(rèn)為資料的存取很少發(fā)生同時(shí)存取的問(wèn)題,因而不作資料庫(kù)層次上的鎖定,為了維護(hù)正確的資料,樂(lè)觀鎖定使用應(yīng)用程式上的邏輯實(shí)現(xiàn)版本控制的解決。
在不實(shí)行悲觀鎖定策略的情況下,資料不一致的情況一但發(fā)生,有幾個(gè)解決的方法,一種是先更新為主,一種是後更新的為主,比較複雜的就是檢查發(fā)生變動(dòng)的資料來(lái)實(shí)現(xiàn),或是檢查所有屬性來(lái)實(shí)現(xiàn)樂(lè)觀鎖定。
Hibernate中透過(guò)版本號(hào)檢查來(lái)實(shí)現(xiàn)後更新為主,這也是Hibernate所推薦的方式,在資料庫(kù)中加入一個(gè)version欄位記錄,在讀取資料時(shí)連同版本號(hào)一同讀取,並在更新資料時(shí)比對(duì)版本號(hào)與資料庫(kù)中的版本號(hào),如果等於資料庫(kù)中的版本號(hào)則予以更新,並遞增版本號(hào),如果小於資料庫(kù)中的版本號(hào)就丟出例外。
實(shí)際來(lái)透過(guò)範(fàn)例瞭解Hibernate的樂(lè)觀鎖定如何實(shí)現(xiàn),首先在資料庫(kù)中新增一個(gè)表格:
CREATE
?
TABLE
?
user
?(
????id?
INT
(
11
)?
NOT
?
NULL
?auto_increment?
PRIMARY
?
KEY
,
????version?
INT
,
????name?
VARCHAR
(
100
)?
NOT
?
NULL
?
default
?
''
,
????age?
INT
);
這個(gè)user表格中的version用來(lái)記錄版本號(hào),以供Hibernate實(shí)現(xiàn)樂(lè)觀鎖定,接著設(shè)計(jì)User類(lèi)別,當(dāng)中必須包括version屬性:
?
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;
}
}
在映射文件的定義方面,則如下所示:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-mapping
PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="onlyfun.caterpillar.User"
table="user"
optimistic-lock="version">
<id name="id" column="id" type="java.lang.Integer">
<generator class="native"/>
</id>
<version name="version"
column="version"
type="java.lang.Integer"/>
<property name="name" column="name" type="java.lang.String"/>
<property name="age" column="age" type="java.lang.Integer"/>
</class>
</hibernate-mapping>
注意<version>標(biāo)籤必須出現(xiàn)在<id>標(biāo)籤之後,接著您可以試著在資料庫(kù)中新增資料,例如:
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();
您可以檢視資料庫(kù)中的資料,每一次對(duì)同一筆資料進(jìn)行更新,version欄位的內(nèi)容都會(huì)自動(dòng)更新,接著來(lái)作個(gè)實(shí)驗(yàn),直接以範(fàn)例說(shuō)明:
// 有使用1者開(kāi)啟了一個(gè)session1
Session session1 = sessionFactory.openSession();
// 在這之後,馬上有另一個(gè)使用者2開(kāi)啟了session2
Session session2 = sessionFactory.openSession();
???????
Integer id = new Integer(1);
// 使用者1查詢(xún)資料 ??????
User userV1 = (User) session1.load(User.class, id);
// 使用者2查詢(xún)同一筆資料
User userV2 = (User) session2.load(User.class, id);
// 此時(shí)兩個(gè)版本號(hào)是相同的
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();
// 此時(shí)由於資料更新,資料庫(kù)中的版本號(hào)遞增了
// 兩筆資料版本號(hào)不一樣了
System.out.println(" v1 v2 "
??????????????? + userV1.getVersion().intValue() + " "
??????????????? + userV2.getVersion().intValue());
???????
// userV2 的 age 資料還是舊的
//?資料更新
userV2.setName("justin");
// 因版本號(hào)比資料庫(kù)中的舊
// 送出更新資料會(huì)失敗,丟出StableObjectStateException例外
tx2.commit();
???????
session1.close();
session2.close();
運(yùn)行以下的程式片段,會(huì)出現(xiàn)以下的結(jié)果:
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)
由於新的版本號(hào)是1,而userV2的版本號(hào)還是0,因此更新失敗丟出StableObjectStateException,您可以捕捉這個(gè)例外作善後處理,例如在處理中重新讀取資料庫(kù)中的資料,同時(shí)將目前的資料與資料庫(kù)中的資料秀出來(lái),讓使用者有機(jī)會(huì)比對(duì)不一致的資料,以決定要變更的部份,或者您可以設(shè)計(jì)程式自動(dòng)讀取新的資料,並比對(duì)真正要更新的資料,這一切可以在背景執(zhí)行,而不用讓您的使用者知道。
要注意的是,由於樂(lè)觀鎖定是使用系統(tǒng)中的程式來(lái)控制,而不是使用資料庫(kù)中的鎖定機(jī)制,因而如果有人特意自行更新版本訊息來(lái)越過(guò)檢查,則鎖定機(jī)制就會(huì)無(wú)效,例如在上例中自行更改userV2的version屬性,使之與資料庫(kù)中的版本號(hào)相同的話就不會(huì)有錯(cuò)誤,像這樣版本號(hào)被更改,或是由於資料是由外部系統(tǒng)而來(lái),因而版本資訊不受控制時(shí),鎖定機(jī)制將會(huì)有問(wèn)題,設(shè)計(jì)時(shí)必須注意。
from : http://caterpillar.onlyfun.net/Gossip/HibernateGossip/HibernateGossip.html