1. 程式人生 > >Hibernate鎖機制

Hibernate鎖機制

業務實現過程中,難免需要保證資料訪問的排他性。如金融系統的日終結算中,我們會針對某個截止點的資料進行處理,在此同時,不希望在結算的這段時間裡(幾秒鐘或者幾個鐘頭),資料再有變動,不然我們的統計也無效了。這種時候,我們為了保證某些資料在某個操作中不被外界修改,就引入了我們hibernate的鎖機制,一旦我們給目標上鎖,其他程式則不能去修改。hibernate中鎖是兩種,悲觀和樂觀鎖。、

悲觀鎖,自然,它指的是對資料被外界(包括本系統當前的其他事物,以及來自外部系統的事務處理)修改,保持保守態度,因此整個處理過程中,資料一致處於被鎖定的狀態【額....還真是夠悲觀的..】,悲觀鎖的實現,往往依靠資料提供的鎖機制(也只有資料庫層提供的鎖機制,才能真正保證排他性,否則本系統中實現加鎖機制,並不能一定保證外部系統不會修改資料)。

下面,這是一個典型的,依賴資料庫實現的悲觀鎖的呼叫:

Sql程式碼 
01.select * from account where name="Erica" for update 
[sql] view plaincopyprint?
01.select * from account where name="Erica" for update 
select * from account where name="Erica" for update  通過for update子句,sql鎖定了account中,所以符合名字等於erica的記錄。即在本次事物提交之前(事物提交時會釋放事物過程中的鎖),外界無法修改這些記錄。hibernate的悲觀鎖,也是基於資料庫的鎖機制實現的,下面程式碼實現了對查詢記錄的加鎖:

Sql程式碼 
01.String hqlStr="from User user where uesr.name='Erica' ";  
02.Query query=session.createQuery(hqlStr);  
03.query.setLockMode("user",LockMode.UPGRADE);  //加鎖  
04.List userList=query.list(); //獲得資料 
[sql] view plaincopyprint?
01.String hqlStr="from User user where uesr.name='Erica' "; 
02.Query query=session.createQuery(hqlStr); 
03.query.setLockMode("user",LockMode.UPGRADE);  //加鎖 
04.List userList=query.list(); //獲得資料 
String hqlStr="from User user where uesr.name='Erica' ";
Query query=session.createQuery(hqlStr);
query.setLockMode("user",LockMode.UPGRADE);  //加鎖
List userList=query.list(); //獲得資料

  當執行後,我們通過show_sql可以看見,原生的sql語句仍然在最後面加上了for update,可以看出hibernate通過資料庫的for update自己來實現了悲觀鎖的機制。

Hibernate的加鎖模式則分為:LockMode.NONE:無鎖    LockMode.WRITE:hibernate在insert和update記錄的時候會自動獲取     LockMode.READ: hibernate在讀取資料的時候會自動獲取。

以上的3中機制,一般由hibernate內部使用,如hibernate為了保證update過程中物件不被外界修改,會在save方法實現中自動為目標物件加上WRITE鎖,這些都是hibernate內部資料的鎖定機制,與資料庫無關。

而LockMode.UPGRADE:利用資料的for update子句加鎖,  LockMode.UPGRADE_NOWAIT: oracle特定的實現,利用oracle的for update nowait子句實現加鎖。

上面這兩種所機制是我們比較常用的,依賴資料庫的悲觀鎖機制。加鎖方式一般有:Criteria.setLockMode ;  Query.setLockMode  ;  Session.lock,注意,只有在查詢開始前(也就是hibernate生成sql前)設定加鎖,才會真正通過資料庫的鎖機制來加鎖處理,否則,資料已經通過不包含for update的子句的select 語句載入了,鎖機制也無從談起。

樂觀鎖,則採取更加寬鬆的機制加鎖。悲觀的大都靠資料庫的for update實現,在保證了獨佔性外,效能會相對消耗較大。對於長事物而言,基本無法承受額樣的開銷。而樂觀鎖則是通過給資料庫增加一個version欄位,通過比較版本資訊,從而實現加鎖機制。下面我們打個比方,就知道了。

資料庫的accout表中,記錄著張三的賬戶上有100塊錢,此時銀行還未上班,張三也未存取或轉賬,這時,樂觀鎖情況下,這個100塊是version=1的,而然在上班之後,銀行操作員a,將賬戶查了出來,在進行扣除,他想著要扣多少錢才好,於是他邊喝咖啡邊想,這時候,操作員b也來了,查到了張三的賬戶也想扣他的錢,這時a想好了,先行扣了張三的50塊,即100-50,然後愉快的提交了,此時張三賬戶的version欄位被+1,成為version=2了。後來b了想好了,扣了它20塊,即100-20,他也想提交,但是突然報出提示,不能提交,緣故是提交的版本必須大於記錄的版本才能執行,哦,他這才知道原來張三的賬戶已經被修改過了,所以才被駁回。

從上面的例子可以看出,樂觀鎖避免了長事務中的資料加鎖的開銷,操作員a和b在操作時,都未加鎖,從而大大提升了大併發量的系統的整體的效能表現。而hibernate在其資料訪問引擎中內建了對樂觀鎖的實現。如果不考慮外部系統對資料庫的更新操作,利用hibernate提供的透明化的樂觀所機制將大大提升生產力。

比如,我們為之前幾篇博文中的User表加上樂觀鎖,即新增optimistic-lock屬性:

Xml程式碼 
01.<hibernate-mapping> 
02.    <class name="com.entity.Uesr" table="User"  dynamic-update="true "   dynamic-insert="true"  optimistic-lock="vsersion"> 
03.<SPAN style="WHITE-SPACE: pre">     </SPAN><version column="version" name="version"  type="java.lang.Integer"/> 
04.......  
05.     </class> 
06.</hibernate-mapping> 
[xml] view plaincopyprint?
01.<hibernate-mapping> 
02.    <class name="com.entity.Uesr" table="User"  dynamic-update="true "   dynamic-insert="true"  optimistic-lock="vsersion"> 
03.<SPAN style="WHITE-SPACE: pre">     </SPAN><version column="version" name="version"  type="java.lang.Integer"/> 
04....... 
05.     </class> 
06.</hibernate-mapping> 
<hibernate-mapping>
    <class name="com.entity.Uesr" table="User"  dynamic-update="true "   dynamic-insert="true"  optimistic-lock="vsersion">
  <version column="version" name="version"  type="java.lang.Integer"/>
......
     </class>
</hibernate-mapping>  上面的optimistic-lock屬性則可選為none(無樂觀鎖);version(通過版本機制實現樂觀鎖);dirty(通過檢查發生變動的屬性實現樂觀鎖); all(通過檢查所有屬性實現樂觀鎖),其中version方式是hibernate官方推薦的方式,所以我在上面的舉例,也是通過version的方式。


下面,我們嘗試去更新User的記錄,程式碼如下:

Java程式碼 
01.Criteria ct=session.createCriteria(User.class);  
02.ct.add(Expression.eq("name","Erica"));  
03. 
04.List userList=ct.list();  
05.User user=(User)userList.get(0);  
06. 
07.Transaction tx=session.beginTransaction();  
08.user.setUserType(1)';  //更新UserType欄位為1  
09.tx.commit(); 
[java] view plaincopyprint?
01.Criteria ct=session.createCriteria(User.class); 
02.ct.add(Expression.eq("name","Erica")); 
03. 
04.List userList=ct.list(); 
05.User user=(User)userList.get(0); 
06. 
07.Transaction tx=session.beginTransaction(); 
08.user.setUserType(1)';  //更新UserType欄位為1  
09.tx.commit(); 
Criteria ct=session.createCriteria(User.class);
ct.add(Expression.eq("name","Erica"));

List userList=ct.list();
User user=(User)userList.get(0);

Transaction tx=session.beginTransaction();
user.setUserType(1)';  //更新UserType欄位為1
tx.commit();
  每次去更新的時候,我們可以發現,資料庫的version一直在加1。如果我們在tx.commit之前,再啟動一個session去對Erica進行更新,以模擬併發更新的話,那麼程式碼就是這樣的:

Java程式碼 
01.Session session=getSession();  
02.Criteria ct=session.createCriteria(User.class);  
03.ct.add(Expression.eq("name","Erica"));  
04. 
05.Session session2=getSession();  
06.Criteria ct2=session.createCriteria(User.class);  
07.ct.add(Expression.eq("name","Erica"));  
08. 
09.List list1=ct.list();  
10.List list2=ct2.list();  
11.User user1=(User)list1.get(0);  
12.User user2=(User)list2.get(0);  
13. 
14.Transaction tx1=session.beginTransaction();  
15.Transaction tx2=session2.beginTransaction();  
16. 
17.user2.setUserType(99);  
18.tx2.commit();  
19.user2.setUserType(1);  
20.tx1.commit(); 
[java] view plaincopyprint?
01.Session session=getSession(); 
02.Criteria ct=session.createCriteria(User.class); 
03.ct.add(Expression.eq("name","Erica")); 
04. 
05.Session session2=getSession(); 
06.Criteria ct2=session.createCriteria(User.class); 
07.ct.add(Expression.eq("name","Erica")); 
08. 
09.List list1=ct.list(); 
10.List list2=ct2.list(); 
11.User user1=(User)list1.get(0); 
12.User user2=(User)list2.get(0); 
13. 
14.Transaction tx1=session.beginTransaction(); 
15.Transaction tx2=session2.beginTransaction(); 
16. 
17.user2.setUserType(99); 
18.tx2.commit(); 
19.user2.setUserType(1); 
20.tx1.commit(); 
Session session=getSession();
Criteria ct=session.createCriteria(User.class);
ct.add(Expression.eq("name","Erica"));

Session session2=getSession();
Criteria ct2=session.createCriteria(User.class);
ct.add(Expression.eq("name","Erica"));

List list1=ct.list();
List list2=ct2.list();
User user1=(User)list1.get(0);
User user2=(User)list2.get(0);

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

user2.setUserType(99);
tx2.commit();
user2.setUserType(1);
tx1.commit();  那麼,這段程式碼執行時會在tx1.commit()處,丟擲StaleObjectStateException異常,指出版本檢查失敗,通過這個異常,我們就可以進行相應的處理。

到這裡,假使看的認真的話,我們大概可以知道下面這幾點,第一,hibernate鎖是用來做什麼的一般用於哪些場合或者系統;第二,什麼是悲觀鎖,樂觀鎖,它們的機制是怎麼樣的。第三,基礎的一些悲觀鎖樂觀鎖的使用方法和實際能解決的問題。希望我說的這三點,大家都能有所苟同。