既然是數據庫的開發,那麼一定有可能出現這樣一種情況,不同的Session讀取了同一個數據,並且針對於同一個數據同時發出更新操作。所以爲了保證數據可以正常的同步進行操作,就需要使用鎖這一概念完成,在Hibernate中提供了兩種鎖的處理機制:
(1)悲觀鎖(Optimistic):指的是使用數據庫中鎖的概念來進行數據的鎖定;
(2)樂觀鎖(Pessimistic):利用程序的邏輯來實現鎖的處理。
1 悲觀鎖
如果要想實現悲觀鎖主要是依靠數據庫的支持完成的。悲觀鎖的操作就是利用了同樣的處理機制完成的,它是在SQL上的處理,悲觀鎖的操作通過程序編碼實現,可以打開查詢接口來查詢操作:
(1)【Query接口】:Query<R> setLockMode(String alias, LockMode lockMode)
(2)【Criteria接口】:public Criteria setLockMode(LockMode lockMode)
在使用此接口方法鎖定數據的時候使用的別名爲HQL中定義的查詢別名,而對於鎖定給出了幾種模式:
(1)讀取時鎖定:public static final LockMode READ
(2)寫入時鎖定:public static final LockMode WRITE
(3)不等待鎖定:public static final LockMode UPGRADE_NOWAIT
範例:觀察鎖定的操作
package org.lks.test;
import org.hibernate.LockMode;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.lks.dbc.HibernateSessionFactory;
public class TestQueryLock {
public static void main(String[] args) {
SessionFactory factory = HibernateSessionFactory.getSessionFactory();
Session sessionA = factory.openSession();
Query queryA = sessionA.createQuery("FROM Member AS m WHERE m.mid=?");
queryA.setLockMode("m", LockMode.UPGRADE_NOWAIT);
queryA.setParameter(0, 1001);
queryA.setCacheable(true);
System.out.println(queryA.uniqueResult());
}
}
Hibernate:
select
member0_.mid as mid1_0_,
member0_.mname as mname2_0_,
member0_.mage as mage3_0_,
member0_.mversion as mversion4_0_
from
hedb.member member0_
where
member0_.mid=? for update
org.lks.pojo.Member@4aac85fa
意味着此時,在當前Session操作的過程之中,數據將採用鎖定的模式,而其它的Session將無法進行直接的更新,如果出現了不能夠更新的情況也不會進行等待。
2 樂觀鎖
它認爲數據庫裏面不應該進行同步的鎖處理,所有的操作都應該通過程序完成處理,那麼樂觀鎖採用的是一種邏輯模式的處理方式,但是如果要想使用樂觀鎖,就必須進行表結構的修改,也就是說除了正常可以使用的數據字段之外,在樂觀鎖的操作過程之中還需要準備出一個用於進行鎖定邏輯判斷的字段,這個字段可以是int或者是date。樂觀鎖的操作流程,以一個產品銷售爲背景:
(1)現在假設有一本書的剩餘庫存爲100本;
(2)現在由“SessionA”讀取這個剩餘的庫存信息,這個時候返回的內容是100,同時返回的版本編號爲1;
(3)同時“SessionB”也讀取了這個剩餘的庫存信息,這個時候返回的內容是100,同時返回的版本編號爲1;
(4)如果此時“SessionA”賣出了20本書,則應該將數據庫中的100修改爲80,但是同時還需要修改版本號爲2;
(5)這個時候“SessionB”也要針對於數據修改,賣出了50本書,但是由於它的版本號是1,數據庫中的版本號是2,那麼將無法進行修改。
範例:數據庫腳本
-- 刪除數據表
DROP TABLE IF EXISTS member;
-- 創建數據表
CREATE TABLE member(
mid INT,
mname varchar(50),
mage INT,
mversion INT,
CONSTRAINT pk_mid PRIMARY KEY(mid)
);
INSERT INTO member(mid,mname,mage,mversion) VALUES(1001,'lks',23,1);
隨後如果要想實現樂觀鎖的配置,需要在Member.hbm.xml文件之中完成,但是mversion是一個由Hibernate自己負責維護的標記,所以不應該出現在屬性的配置上。
範例:觀察Member.hbm.xml文件的修改
<version name="mversion" column="mversion" type="integer"></version>
下面將產生兩個Session對象對數據進行同時的更新操作。
範例:編寫程序進行測試
package org.lks.test;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.lks.dbc.HibernateSessionFactory;
import org.lks.pojo.Member;
public class TestQueryLock {
public static void main(String[] args) {
SessionFactory factory = HibernateSessionFactory.getSessionFactory();
Session sessionA = factory.openSession();
Session sessionB = factory.openSession();
Query queryA = sessionA.createQuery("FROM Member AS m WHERE m.mid=?");
Query queryB = sessionB.createQuery("FROM Member AS m WHERE m.mid=?");
queryA.setParameter(0, 1001);
queryB.setParameter(0, 1001);
Member mea = (Member)queryA.uniqueResult();
Member meb = (Member)queryB.uniqueResult();
mea.setMage(40);
sessionA.beginTransaction().commit();
meb.setMage(60);
sessionB.beginTransaction().commit();
}
}
Hibernate:
select → queryA.uniqueResult();
member0_.mid as mid1_0_,
member0_.mversion as mversion2_0_,
member0_.mname as mname3_0_,
member0_.mage as mage4_0_
from
hedb.member member0_
where
member0_.mid=?
Hibernate:
select →queryB.uniqueResult();
member0_.mid as mid1_0_,
member0_.mversion as mversion2_0_,
member0_.mname as mname3_0_,
member0_.mage as mage4_0_
from
hedb.member member0_
where
member0_.mid=?
Hibernate:
update →mea.setMage(40);
hedb.member
set
mversion=?,
mname=?,
mage=?
where
mid=?
and mversion=?
Hibernate:
update →meb.setMage(60);
hedb.member
set
mversion=?,
mname=?,
mage=?
where
mid=?
and mversion=?
七月 04, 2020 4:40:45 下午 org.hibernate.internal.SessionImpl$5 mapManagedFlushFailure
ERROR: HHH000346: Error during managed flush [Batch update returned unexpected row count from update [0]; actual row count: 0; expected: 1]
Exception in thread "main" org.hibernate.StaleStateException: Batch update returned unexpected row count from update [0]; actual row count: 0; expected: 1
此時在同一個線程之中,兩個Session修改了同一條操作數據。
下面更換爲日期時間進行版本號的操作設置。
範例:修改數據庫的腳本
-- 刪除數據表
DROP TABLE IF EXISTS member;
-- 創建數據表
CREATE TABLE member(
mid INT,
mname varchar(50),
mage INT,
mversion DATATIME,
CONSTRAINT pk_mid PRIMARY KEY(mid)
);
INSERT INTO member(mid,mname,mage,mversion) VALUES(1001,'lks',23,'2020-04-04 10:10:10');
範例:配置樂觀鎖
@Version
@Column(name="mversion")
public Integer getMversion() {
return this.mversion;
}
樂觀鎖的邏輯處理部分太麻煩了,而且如果只是要鎖定數據,不如使用悲觀鎖。