悲觀鎖
在應用程式中顯示地為資料資源加鎖.悲觀鎖假定目前事務操縱資料資源時,肯定還會有其它事務同時通路該資料資源,為了避免目前事務的操作受到幹擾,先鎖定資源.盡管悲觀鎖能防止丢失更新和不可重複讀這類并發問題,但會影響并發性能.
樂觀鎖
假定目前事務操縱資料資源時,不會有其它事務同時通路該資料資源,是以完全依靠資料庫的隔離級别來自動管理鎖的工作.應用程式采用版本控制手段來避免可能出現的并發問題.
悲觀鎖
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更新過了,是以抛出了紅色的異常,我們可以在實際應用中處理這個異常,例如在進行中重新讀取資料庫中的資料,同時将目前的資料與資料庫中的資料展示出來,讓使用者有機會比較一下,或者設計程式自動讀取新的資料