業務邏輯的實現過程中,往往需要保證數據訪問的排他性。因此,我們就需要通過一些機制來保證這些數據在某個操作過程中不會被外界修改,這樣的機制,在這里,也就是所謂的“鎖”,即給我們選定的目標數據上鎖,使其無法被其它程序修改。
Hibernate 支持兩種鎖機制:
1. 悲觀鎖(Pessimistic Locking)
從加載對象就開始鎖定。修改過程中一直是鎖。直到事務commit()提交后再解鎖。
session.load(Info.class,"p003",LockOptions.UPGRADE);
public class TestPessimisticLock extends TestCase { @Test public void testLock1(){ Session session = null; try { session = HibernateUtil.getSession();//開始鎖定,下面的testLock2不能執行 session.beginTransaction(); Info data = session.load(Info.class, "p003", LockOptions.UPGRADE); data.setName("1111111"); session.getTransaction().commit();//執行以后才解鎖,這時testLock2才可以執行 } catch (Exception e) { e.printStackTrace(); session.getTransaction().rollback(); } finally{ HibernateUtil.closeSession(); } } @Test public void testLock2(){ Session session = null; try { session = HibernateUtil.getSession(); session.beginTransaction(); Info data = session.load(Info.class, "p003", LockOptions.UPGRADE); data.setName("2222222"); session.getTransaction().commit(); } catch (Exception e) { e.printStackTrace(); session.getTransaction().rollback(); } finally{ HibernateUtil.closeSession(); } } }
2. 樂觀鎖(Optimistic Locking)
並不是真的鎖,是在提交時間進行沖突檢測。把里面的內容與剛開始讀取的內容對照一下,有問題就拋異常。相對於悲觀鎖而言,樂觀鎖機制采取了更加寬松的加鎖機制。
悲觀鎖大多數情況下依靠數據庫的鎖機制實現,以保證操作最大程度的獨占性。但隨之而來的就是數據庫性能的大量開銷,特別是對長事務而言,這樣的開銷往往無法承受。樂觀鎖機制在一定程度上解決了這個問題。樂觀鎖,大多是基於數據版本(Version)記錄機制實現。何謂數據版本?即為數據增加一個版本標識,在基於數據庫表的版本解決方案中,一般是通過為數據庫表增加一個"version"字段來實現。
樂觀鎖的工作原理 :
讀取出數據時,將此版本號一同讀出,之后更新時,對此版本號加一。此時,將提交數據的版本數據與數據庫表對應記錄的當前版本信息進行比對,如果提交的數據版本號大於數據庫表當前版本號,則予以更新,否則認為是過期數據。
配置:
1.在數據庫表中加一個字段version
如果是數據庫后加的version字段,那么實體類中必須要加上version並生成get、set方法
public class Info implements java.io.Serializable { private String code; private Nation nation; private String name; private Boolean sex; private Date birthday; private Set families = new HashSet(0); private Set works = new HashSet(0); private int version;//實體類中也要有version,並生成getter和setter public int getVersion() { return version; } public void setVersion(int version) { this.version = version; }
2.在映謝文件中配置<version name="version"> 這里注意version的位置,一定是要放置在id的后面
<?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"> <!-- Generated 2017-3-11 10:12:32 by Hibernate Tools 5.2.0.CR1 --> <hibernate-mapping> <class name="com.itnba.maya.model.Info" table="info" catalog="mydb" optimistic-lock="version"> <cache usage="read-only"/> <id name="code" type="string"> <column name="Code" length="50" /> <generator class="assigned" /> </id> <!-- 配置version,位置放在<id></id>下面 --> <version name="version"></version>
<many-to-one name="nation" class="com.itnba.maya.model.Nation" fetch="select"> <column name="Nation" length="50" /> </many-to-one> <property name="name" type="string"> <column name="Name" length="50" /> </property> <property name="sex" type="java.lang.Boolean"> <column name="Sex" /> </property> <property name="birthday" type="timestamp"> <column name="Birthday" length="19" /> </property> </class> </hibernate-mapping>
配置完成后代碼不用改變
由樂觀鎖引發的問題
當兩個不同的事務同時讀取到一條數據並進行修改時,這個時候程序就會拋出org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect)異常。
這里同樣有兩種情況
一種是兩個不同的事務的情況
@Test public void testTransation1(){ Session session1 = null; Session session2 = null; try{ session1 = HibernateUtil.getSession(); session2 = HibernateUtil.getSession(); Info info1= session1.load(Info.class, "p003"); Info info2 = session2.load(Info.class, "p003"); Transaction tx1 = session1.beginTransaction(); info1.setName("2222222"); tx1.commit(); Transaction tx2 = session2.beginTransaction(); info2.setName("11111111"); tx2.commit(); System.out.println("事務2提交"); }catch(Exception e){ e.printStackTrace(); }finally{ if(session1 != null){ session1.close(); } if(session2 != null){ session2.close(); } } }
事務2提交時發現version的值不一樣,這個時候就會拋出org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect)異常.
第二種情況是子事務的情況
@Test public void testTransation2(){ Session session1 = null; Session session2 = null; try{ session1 = HibernateUtil.getSession(); session2 = HibernateUtil.getSession(); Info info1= session1.load(Info.class, "p003"); Info info2 = session2.load(Info.class, "p003"); Transaction tx1 = session1.beginTransaction(); Transaction tx2 = session2.beginTransaction(); info2.setName("11111111"); tx2.commit(); info1.setName("2222222"); tx1.commit(); }catch(Exception e){ e.printStackTrace(); }finally{ if(session1 != null){ session1.close(); } if(session2 != null){ session2.close(); } } }
我們發現事物2被包裹在事務1里面,如果Dir被配置為延遲加載(hibnernate默認就是延遲加載的)的,這個時候在事務1進行提交的時候,會先去數據庫進行查詢一下,再進行更新操作。
如果Dir被配置為非延遲加載(lazy="false")的,這個時候事務1在提交的時候就不會先去查詢數據庫,而是直接提交,在提交的時候發現version不匹配,因而也會拋出org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect)異常 .
解決辦法
1、捕獲StaleObjectStateException異常,提示數據過時已被修改,讓用戶重新提交
2、盡量從業務方面去減小事務塊,事務塊越大,由樂觀鎖引起的問題的概率就越大