Hibernate悲觀鎖/樂觀鎖

若是須要保證數據訪問的排它性,則需對目標數據加「鎖」,使其沒法被其它程序修改html

一,悲觀鎖java

對數據被外界(包括本系統當前的其它事務和來自外部系統的事務處理)修改持保守態度,經過數據庫提供的鎖機制實現數據庫

最經常使用的,是對查詢進行加鎖(LockMode.UPGRADE和LockMode.UPGRADE_NOWAIT):session

public class Test {
	public static void main(String[] args) {
		Configuration conf = new Configuration();  
        SessionFactory sessionFactory = conf.configure().buildSessionFactory(); 
        
        Session sess = sessionFactory.openSession();
        Transaction tran = sess.beginTransaction();
        
        String hql = "from User where id = 1";
        Query query = sess.createQuery(hql);
        query.setLockOptions(LockOptions.UPGRADE);
        List<User> list = query.list();
        for(User user : list){
        	System.out.print(user.getName()+" ");
        }
        System.out.println();

        tran.commit();
        
        sess.close(); 
	}
}

Hibernate會在生成的SQL後面加上for update子句:app

Hibernate: select user0_.id as id0_, user0_.name as name0_, user0_.age as age0_ 
		from TEST_USER user0_ where user0_.id=1 for update
longlong 

經過for update子句,這條SQL鎖定了TEST_USER表中符合檢索條件的記錄,本次事務提交前,外界沒法修改這些記錄,事務提交時會釋放事務過程當中的鎖性能

Hibernate提供了2個鎖對象,LockMode和LockOptions:ui

經過LockOptions的源代碼,能夠發現LockOptions只是LockMode的簡單封裝(在LockMode的基礎上提供了timeout和scope):this

......
/**
 * NONE represents LockMode.NONE (timeout + scope do not apply)
 */
public static final LockOptions NONE = new LockOptions(LockMode.NONE);

/**
 * READ represents LockMode.READ (timeout + scope do not apply)
 */
public static final LockOptions READ = new LockOptions(LockMode.READ);

/**
 * UPGRADE represents LockMode.UPGRADE (will wait forever for lock and
 * scope of false meaning only entity is locked)
 */
public static final LockOptions UPGRADE = new LockOptions(LockMode.UPGRADE);

public LockOptions(){}

public LockOptions( LockMode lockMode) {
	this.lockMode = lockMode;
}
.....
public static final int NO_WAIT = 0;

/**
 * Indicates that there is no timeout for the acquisition.
 * @see #getTimeOut
 */
public static final int WAIT_FOREVER = -1;

private int timeout = WAIT_FOREVER;

private boolean scope=false;
......

LockOptions提供的加鎖機制要比LockMode少不少,可是LockMode多出的加鎖機制通常只是供Hibernate內部實現使用的
spa

保證了操做的獨佔性,但嚴重影響數據庫性能.net


二,樂觀鎖

樂觀鎖大多基於數據版本記錄機制實現,既爲數據增長一個版本標識

在數據庫中增長version列,用來記錄每行數據的版本

Hibernate配置文件中,version節點須要在id節點以後並緊跟id節點

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
		"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
		"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd" >

<hibernate-mapping>
    <class name="com.po.User" 
        table="TEST_USER">
        <id name="id" column="id" type="java.lang.Integer">
            <generator class="assigned"/>
        </id>
        <version name="version"
        		column="version"
        		type="java.lang.Integer"/>
        <property name="name"
				column="name"
            	type="java.lang.String"
            	not-null="true"
           	 	unique="true"
            	length="20"/>
        <property name="age"
            	column="age"
            	type="java.lang.Integer"
            	not-null="true"
            	unique="false"
            	length="0"/>
    </class>
</hibernate-mapping>

每次更新User對象時時,對應行的version字段都在增長

public class Test {
	public static void main(String[] args) {
		Configuration conf = new Configuration();  
        SessionFactory sessionFactory = conf.configure().buildSessionFactory();
        
        Session sess1=sessionFactory.openSession();
        Session sess2=sessionFactory.openSession();
		try{
	        User user1 = (User)sess1.get(User.class, 1);
	        User user2 = (User)sess2.get(User.class, 1);
	  
	        System.out.println("v1="+user1.getVersion()+"--v2="+user2.getVersion());
	        
	        Transaction tx1 = sess1.beginTransaction();
	        Transaction tx2 = sess2.beginTransaction();
	        
	        user1.setName("ll");
	        tx1.commit();
	        
	        System.out.println("v1="+user1.getVersion()+"--v2="+user2.getVersion());
	        
	        user2.setName("LL");
	        tx2.commit();
		}catch(Exception e){
			e.printStackTrace();
		}finally{
	        sess1.close();
	        sess2.close();
		}
	}
}

運行結果以下,能夠看到因爲tx1提交時,version字段已經被修改,tx2提交時會拋出異常:

Hibernate: select user0_.id as id0_0_, user0_.version as version0_0_, user0_.name as name0_0_, user0_.age as age0_0_ 
		from TEST_USER user0_ where user0_.id=?
Hibernate: select user0_.id as id0_0_, user0_.version as version0_0_, user0_.name as name0_0_, user0_.age as age0_0_ 
		from TEST_USER user0_ where user0_.id=?
v1=0--v2=0
Hibernate: update TEST_USER set version=?, name=?, age=? where id=? and version=?
v1=1--v2=0
Hibernate: update TEST_USER set version=?, name=?, age=? where id=? and version=?
Exception in thread "main" org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction 
		(or unsaved-value mapping was incorrect): [com.po.User#1]
	at org.hibernate.persister.entity.AbstractEntityPersister.check(AbstractEntityPersister.java:1932)
	at org.hibernate.persister.entity.AbstractEntityPersister.update(AbstractEntityPersister.java:2576)
	at org.hibernate.persister.entity.AbstractEntityPersister.updateOrInsert(AbstractEntityPersister.java:2476)
	at org.hibernate.persister.entity.AbstractEntityPersister.update(AbstractEntityPersister.java:2803)
	at org.hibernate.action.EntityUpdateAction.execute(EntityUpdateAction.java:113)
	at org.hibernate.engine.ActionQueue.execute(ActionQueue.java:273)
	at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:265)
	at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:185)
	at org.hibernate.event.def.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:321)
	at org.hibernate.event.def.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:51)
	at org.hibernate.impl.SessionImpl.flush(SessionImpl.java:1216)
	at org.hibernate.impl.SessionImpl.managedFlush(SessionImpl.java:383)
	at org.hibernate.transaction.JDBCTransaction.commit(JDBCTransaction.java:133)
	at com.test.Test.main(Test.java:43)

除了使用version做爲版本標識,還可使用timestamp做爲版本標識

timestamp節點沒有type屬性:

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
		"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
		"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd" >

<hibernate-mapping>
    <class name="com.po.User" 
        table="TEST_USER">
        <id name="id" column="id" type="java.lang.Integer">
            <generator class="assigned"/>
        </id>
        <timestamp name="updatetime"
        		column="updatetime"/>
        <property name="name"
				column="name"
            	type="java.lang.String"
            	not-null="true"
           	 	unique="true"
            	length="20"/>
        <property name="age"
            	column="age"
            	type="java.lang.Integer"
            	not-null="true"
            	unique="false"
            	length="0"/>
    </class>
</hibernate-mapping>

在某些狀況下,不容許修改數據庫的表結構,此時Hibernate也有相應的處理手段:

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
		"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
		"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd" >

<hibernate-mapping>
    <class name="com.po.User" 
    		table="TEST_USER"
        	optimistic-lock="all"
        	dynamic-update="true"
        	dynamic-insert="true"
        	>
        <id name="id" column="id" type="java.lang.Integer">
        	<generator class="assigned"/>
        </id>
        <property name="name"
				column="name"
            	type="java.lang.String"
            	not-null="true"
           	 	unique="true"
            	length="20"/>
        <property name="age"
            	column="age"
            	type="java.lang.Integer"
            	not-null="true"
            	unique="false"
            	length="0"/>
    </class>
</hibernate-mapping>

此時Hibernate將使用User類的全部字段做爲版本控制信息

樂觀鎖相較悲觀鎖提升了很多性能,可是有必定的侷限性,因爲是在應用層加鎖,若是此時在數據中直接修改數據(或其它應用程序修改數據庫中的數據),應用層是沒法感知到這種變化的,須要配合其它技術手段一塊兒使用

相關文章
相關標籤/搜索