天天看點

Hibernate悲觀鎖/樂觀鎖

如果需要保證資料通路的排它性,則需對目标資料加“鎖”,使其無法被其它程式修改

一,悲觀鎖

對資料被外界(包括本系統目前的其它事務和來自外部系統的事務處理)修改持保守态度,通過資料庫提供的鎖機制實作

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

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子句:

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:

通過LockOptions的源代碼,可以發現LockOptions隻是LockMode的簡單封裝(在LockMode的基礎上提供了timeout和scope):

......
/**
 * 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内部實作使用的

保證了操作的獨占性,但嚴重影響資料庫性能

二,樂觀鎖

樂觀鎖大多基于資料版本記錄機制實作,既為資料增加一個版本辨別

在資料庫中增加version列,用來記錄每行資料的版本

Hibernate悲觀鎖/樂觀鎖

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作為版本辨別

Hibernate悲觀鎖/樂觀鎖

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類的所有字段作為版本控制資訊

樂觀鎖相較悲觀鎖提高了不少性能,但是有一定的局限性,由于是在應用層加鎖,如果此時在資料中直接修改資料(或其它應用程式修改資料庫中的資料),應用層是無法感覺到這種變化的,需要配合其它技術手段一起使用

繼續閱讀