在使用Hibernate的過程中,我們會遇到多人對同一個資料同時進行修改,這個時候就會産生髒資料,造成資料的不一緻性。為了避免更新資料的丢失,Hibernate采用了鎖的機制。
Hibernate提供了兩種鎖的機制:悲觀鎖和樂觀鎖
悲觀鎖:在資料有加載的時候就給其進行加鎖,直到該鎖被釋放掉,其他使用者才可以進行修改;
樂觀鎖:在對資料進行修改的時候,對資料采用版本号或者時間戳等方式來比較,資料是否一緻性來實作加鎖;
一、悲觀鎖
悲觀鎖是依靠資料庫提供的鎖機制。Hibernate是通過使用資料庫的for update子句實作了悲觀鎖機制。
Hibernate有如下五種加鎖機制
1、 LockMode.NONE:無鎖機制;
2、 LockMode.WRITE:Hibernate在Insert和Update記錄的時候會自動擷取
3、 LockMode.READ:Hibernate在讀取記錄的時候會自動擷取
4、 LockMode.UPGRADE:利用資料庫的for update子句加鎖
5、 LockMode.UPGRADE_NOWAIT:Oracle的特定實作,利用Oracle的for update nowait子句實作加鎖
悲觀加鎖一般通過以下三種方法實作:
1、Criteria.setLockMode
2、Query.setLockMode
3、Session.lock
public void query(int id){
Session session = HibernateUtil.getSession();
Transaction tx = session.beginTransaction();
String hql = "from Users as u where id= :id";
List list = session.createQuery(hql)
.setLockMode("u", LockMode.UPGRADE) //執行加鎖
.setInteger("id", id)
.list();
for(Iterator iterator = list.iterator();iterator.hasNext();){
Users users = (Users) iterator.next();
System.out.println(users.getBirthday());
}
}
select users0_.id as id0_, users0_.ver as ver0_,
users0_.birthday as birthday0_, users0_.first_name as first4_0_, users0_.last_name as last5_0_ from Users users0_
with (updlock, rowlock) where users0_.id=?
悲觀鎖在對資料進行加鎖後,會一直“霸占”該資料,直到釋放掉,其他使用者才可以對該資料進行更新。這裡就存在一個問題,如果它一直占着不放,那麼其他使用者永遠也不可能對該資料進行更新,這樣就很不利于并發了。對于這個問題的解決方案,可以利用樂觀鎖。
二、樂觀鎖
樂觀鎖大多是基于資料版本記錄機制實作。何謂資料版本?即為資料增加一個版本辨別,在基于資料庫表的版本解決方案中,一般是通過為資料庫表增加一個“version”字段來實作。讀取出資料時,将此版本号一同讀出,之後更新時,對此版本号加一。此時,将送出資料的版本資料與資料庫表對應記錄的目前版本資訊進行比對,如果送出的資料版本号大于資料庫表目前版本号,則予以更新,否則認為是過期資料。
我們可以通過class描述符的optimistic-lock屬性結合version描述符指定樂觀鎖。
1. none:無樂觀鎖
2. version:通過版本機制實作樂觀鎖
3. dirty:通過檢查發生變動過的屬性實作樂觀鎖
4. all:通過檢查所有屬性實作樂觀鎖
在實作樂觀鎖的持久化類我們需要為該持久化類增加一個version屬性,并且提供相應的getter和setter方法。如下
public class Users {
private int id;
private Date birthday;
private Name name;
private int version;
//舍掉getter和setter方法
}
<hibernate-mapping package="com.hibernate.domain">
<class name="Users" optimistic-lock="version">
<id name="id">
<generator class="native" />
</id>
<version name="version" />
<property name="birthday" />
<!-- 映射元件元素 -->
<component name="name">
<!-- 映射元件的name屬性指向包含實體 -->
<property name="firstName" column="first_name"/>
<property name="lastName" column="last_name"/>
</component>
</class>
</hibernate-mapping>
注意:version 節點必須出現在ID 節點之後。
在這裡我們聲明了一個version屬性,該屬性用于存放使用者的版本資訊。我們對user表每一次更新操作,都會引起version屬性的變化:加1。如果我們嘗試在tx.commit 之前,啟動另外一個Session,對名為同一個使用者進行操作,就是并發更新的情形了:
public void update(){
//開啟事務tx1
Session session1 = HibernateUtil.getSession();
Transaction tx1 = session1.beginTransaction();
Users users1 = (Users) session1.get(Users.class, 1); //擷取id為1的使用者
//開啟事務tx2
Session session2 = HibernateUtil.getSession();
Transaction tx2 = session2.beginTransaction();
Users users2 = (Users) session2.get(Users.class, 1); //擷取id為1的使用者
users1.getName().setFirstName("first name1");
users2.getName().setFirstName("first name2");
tx1.commit(); //..........1
tx2.commit(); //..........2
session1.close();
session2.clear();
}
執行以上代碼,代碼将在.....2處抛出StaleObjectStateException異常,并指出版本檢查失敗。

在這裡是先送出者成功,後送出者失敗。目前事務正在試圖送出一個過期資料。通過捕捉這個異常,我們就可以在樂觀鎖校驗失敗時進行相應處理。