天天看點

Hibernate的 悲觀鎖 樂觀鎖

悲觀鎖

在應用程式中顯示地為資料資源加鎖.悲觀鎖假定目前事務操縱資料資源時,肯定還會有其它事務同時通路該資料資源,為了避免目前事務的操作受到幹擾,先鎖定資源.盡管悲觀鎖能防止丢失更新和不可重複讀這類并發問題,但會影響并發性能.

樂觀鎖

假定目前事務操縱資料資源時,不會有其它事務同時通路該資料資源,是以完全依靠資料庫的隔離級别來自動管理鎖的工作.應用程式采用版本控制手段來避免可能出現的并發問題.

悲觀鎖

LockMode類表示的幾種鎖定模式

鎖定模式 描述
LockMode.NONE 如果緩存中存在對象,直接傳回該對象的引用,否則通過select語句到資料庫中加載該對象,預設值.
LockMode.READ 不管緩存中是否存在對象,總是通過select語句到資料庫中加載該對象,如果映射檔案中設定了版本元素,就執行版本檢查,比較緩存中的對象是否和資料庫中對象版本一緻.
LockMode.UPGRADE 不管緩存中是否存在對象,總是通過select語句到資料庫中加載該對象,如果映射檔案中設定了版本元素,就執行版本檢查,比較緩存中的對象是否和資料庫中對象的版本一緻,如果資料庫系統支援悲觀鎖(如Oracle/MySQL),就執行select...for update語句,如果不支援(如Sybase),執行普通select語句.
LockMode.UPGRADE_NOWAIT 和LockMode.UPGRADE具有同樣功能,此外,對于Oracle等支援update nowait的資料庫,執行select...for update nowait語句,nowait表明如果執行該select語句的事務不能立即獲得悲觀鎖,那麼不會等待其它事務釋放鎖,而是立刻抛出鎖定異常.
LockMode.WRITE 儲存對象時會自動使用這種鎖定模式,僅供Hibernate内部使用,應用程式中不應該使用它.
LockMode.FORCE 強制更新資料庫中對象的版本屬性,進而表明目前事務已經更新了這個對象.
案例:
public ChainOrders getLock(int ordersId) {
        String hql = "from ChainOrders Lorders where ordersId = " + ordersId;
        Query query = sessionFactory.getCurrentSession().createQuery(hql);
        
        //Hibernate 3.6以前版本用LockMode
        query.setLockMode("Lorders", LockMode.UPGRADE_NOWAIT);

        //Hibernate 4.0以後版本推薦使用LockOptions
        //query.setLockOptions(LockOptions.UPGRADE);

        List<ChainOrders> ordersList = query.list();
        return ordersList.size() > 0 ? ordersList.get(0) : null;


MySQL中select * for update鎖表的問題
由于InnoDB預設是Row-Level Lock,是以隻有「明确」的指定主鍵,MySQL才會執行Row lock (隻鎖住被選取的資料例) ,否則MySQL将會執行Table Lock (将整個資料表單給鎖住)。
舉個例子:
假設有個表單products ,裡面有id跟name二個欄位,id是主鍵。

例1: (明确指定主鍵,并且有此筆資料,row lock)
SELECT * FROM products WHERE id='3' FOR UPDATE;
SELECT * FROM products WHERE id='3' and type=1 FOR UPDATE;


例2: (明确指定主鍵,若查無此筆資料,無lock)
SELECT * FROM products WHERE id='-1' FOR UPDATE;


例2: (無主鍵,table lock)
SELECT * FROM products WHERE name='Mouse' FOR UPDATE;


例3: (主鍵不明确,table lock)
SELECT * FROM products WHERE id<>'3' FOR UPDATE;


例4: (主鍵不明确,table lock)
SELECT * FROM products WHERE id LIKE '3' FOR UPDATE;


注1: FOR UPDATE僅适用于InnoDB,且必須在交易區塊(BEGIN/COMMIT)中才能生效。
注2: 要測試鎖定的狀況,可以利用MySQL的Command Mode ,開二個視窗來做測試。
           

樂觀鎖

樂觀鎖假定目前事務操縱資料資源時,不會有其他事務同時通路該資料資源,是以不作資料庫層次上的鎖定。為了維護正确的資料,樂觀鎖使用應用程式上的版本控制(由程式邏輯來實作的)來避免可能出現的并發問題。

在該實體類的版本屬性上加入注解@Version

@Version
private int version;
    public int getVersion() {
        return version;
    }
    public void setVersion(int version) {
        this.version = version;
    }


在表裡加上一個version字段,記錄目前的版本号,更新資料時version會加1。将送出資料的版本号與資料庫表對應記錄的目前版本号進行比對,如果送出的資料版本号大于資料庫表目前版本号,則更新,否則就報錯。 
比如 
事物A取出一條記錄,目前版本号為0,更改記錄後版本号增加1。如果在事物A送出前有個事物B更新了該資料,資料庫内的版本号變為了1,A再送出時就會報錯。

           
/* 
 * 模拟多個session操作student資料表 
 */  
         
Sessionsession1=sessionFactory.openSession();  
Session session2=sessionFactory.openSession();  
Studentstu1=(Student)session1.createQuery("from Student swheres.name='tom11'").uniqueResult();  
        
Studentstu2=(Student)session2.createQuery("from Student swheres.name='tom11'").uniqueResult();  
         
//這時候,兩個版本号是相同的   
System.out.println("v1="+stu1.getVersion()+"--v2="+stu2.getVersion());  
         
Transactiontx1=session1.beginTransaction();  
stu1.setName("session1");  
tx1.commit();  
        
//這時候,兩個版本号是不同的,其中一個的版本号遞增了   
System.out.println("v1="+stu1.getVersion()+"--v2="+stu2.getVersion());  
         
Transactiontx2=session2.beginTransaction();  
stu2.setName("session2");  
tx2.commit();  
           

運作結果:

Hibernate: insert into studentVersion (ver, name,id) values (?, ?, ?)

Hibernate: select student0_.id as id0_, student0_.ver as ver0_, student0_.nameas name0_ from studentVersion student0_ where student0_.name='tom11'

Hibernate: select student0_.id as id0_, student0_.ver as ver0_, student0_.nameas name0_ from studentVersion student0_ where student0_.name='tom11'

v1=0--v2=0

Hibernate: update studentVersion set ver=?, name=? where id=? and ver=?

v1=1--v2=0

Hibernate: update studentVersion set ver=?, name=? where id=? and ver=?

Exception in thread "main" org.hibernate.StaleObjectStateException:Row was updated or deleted by another transaction (or unsaved-value mapping wasincorrect): [Version.Student#4028818316cd6b460116cd6b50830001]

 可以看到,第二個“使用者”session2修改資料時候,記錄的版本号已經被session1更新過了,是以抛出了紅色的異常,我們可以在實際應用中處理這個異常,例如在進行中重新讀取資料庫中的資料,同時将目前的資料與資料庫中的資料展示出來,讓使用者有機會比較一下,或者設計程式自動讀取新的資料

繼續閱讀