并發控制
?????? 當數據庫系統采用Read Committed隔離級別時,會導致不可重復讀取和兩次更新丟失的并發問題,可以在應用程序中采用鎖機制來避免這類問題的產生。
?????? 從應用程序的角度上看,鎖可以分為樂觀鎖和悲觀鎖兩大類。
悲觀鎖
?????? 在多個客戶端可能讀取同一筆數據或同時更新一筆數據的情況下,必須要有訪問控制的手段,防止同一個數據被修改而造成混亂,最簡單的手段就是對數據進行鎖定。在自己進行數據讀取或更新等動作時,鎖定其他客戶端不能對同一筆數據進行任何的動作。
?????? 悲觀鎖(Pessimistic Locking),如其名稱所示,悲觀地認定每次資料存取時,其他的客戶端也會存取同一筆數據,因此將會鎖住該筆數據,直到自己操作完成后再解除鎖。
?????? 悲觀鎖假定任何時刻存取數據時,都可能有另一個客戶也正在存取同一筆數據,因而對數據采取了數據庫層次的鎖定狀態,在鎖定的時間內其他的客戶不能對數據進行存取。對于單機或小系統而言,這并不成問題,然而如果是在網絡上的系統,同時間會有許多訪問的機器,如果每一次讀取數據都造成鎖定,其后繼的存取就必須等待,這將造成效能上的問題,造成后繼使用者的長時間等待。
?????? 悲觀鎖通常透過系統或數據庫本身的功能來實現,依賴系統或數據庫本身提供的鎖機制。Hibernate即是如此,可以利用Query或Criteria的setLockMode()方法來設定要鎖定的表或列及其鎖模式,可設定的鎖模式有以下幾個。
?????? LockMode.UPGRADE:利用數據庫的for update子句進行鎖定。
?????? LockMode.UPGRADE_NOWAIT:使用for update nowait子句進行鎖定,在Oracle數據庫中使用。
?????? 下面來實現一個簡單的例子,測試一下采用悲觀鎖時數據庫是如何進行操作的。
?????? 首先來完成一個實體對象——User,該對象包含了id,name和age三個屬性,實現的方法如清單14.1所示。
?????? 清單14.1??? User對象的實現
package cn.hxex.hibernate.lock;
public class User {
?????? private String id;
?????? private String name;
?????? private Integer age;
??????
?????? // 省略了getter和setter方法
?????? ……
}
?????? 接下來就是映射文件的配置,由于該映射文件沒有涉及到任何與其他對象的關聯配置,所以實現的方法也非常簡單,代碼如清單14.2所示。
?????? 清單14.2??? User映射文件的實現
<?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 package="cn.hxex.hibernate.lock">
?????? <class name="User" table="USERINFO">
????????????? <id name="id" column="userId">
????? ?????????? <generator class="uuid.hex"/>
??? ?????? </id>
?????????????
????????????? <property name="name" column="name" type="java.lang.String"/>
????????????? <property name="age" column="age" type="java.lang.Integer"/>
?????? </class>
</hibernate-mapping>
?????? 另外一件重要的工作就是Hibernate的配置文件了,在這個配置文件中包含了連接數據庫的參數以及其他一些重要的參數,實現的方法如清單14.3所示。
?????? 清單14.3??? Hibernate配置文件的實現
<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE hibernate-configuration PUBLIC
????????? "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
????????? "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
?????? <session-factory>
????????????? <!-- 數據庫的URL -->
????????????? <!-- property name="hibernate.connection.url">
????????????? jdbc:oracle:thin:@192.168.10.121:1521:HiFinance</property-->
?????? ?????? <property name="hibernate.connection.url">
?????? jdbc:mysql://localhost:3306/lockdb?useUnicode=true&characterEncoding=utf8&autoReconnect=true&autoReconnectForPools=true
??????? </property>
?????????????
????????????? <!-- 數據庫的驅動程序 -->
????????????? <!-- property name="hibernate.connection.driver_class">
????????????? oracle.jdbc.driver.OracleDriver</property-->
????????????? <property name="hibernate.connection.driver_class">
?????????? com.mysql.jdbc.Driver
??????? </property>
????????????? <!-- 數據庫的用戶名 -->
????????????? <property name="hibernate.connection.username">lockdb</property>
????????????? <!-- 數據庫的密碼 -->
????????????? <property name="hibernate.connection.password">lockdb</property>
????????????? <!-- 數據庫的Dialect -->
????????????? <!-- property name="hibernate.dialect">
????????????? org.hibernate.dialect.Oracle9Dialect</property -->
????????????? <property name="hibernate.dialect">
????????????? org.hibernate.dialect.MySQLDialect</property>
????????????? <!-- 輸出執行的SQL語句 -->
????????????? <property name="hibernate.show_sql">true</property>
?????????????
????????????? <property name="hibernate.current_session_context_class">thread</property>
?????????????
????????????? <property name="hibernate.hbm2ddl.auto">update</property>
????????????? <!-- HBM文件列表 -->
????????????? <mapping resource="cn/hxex/hibernate/lock/User.hbm.xml" />
?????? </session-factory>
</hibernate-configuration>
?????? 最后要實現的就是測試主程序了,在測試主程序中包含了Hibernate的初始化代碼以及悲觀鎖的測試方法。測試主程序的實現方法如清單14.4所示。
?????? 清單14.4??? 測試主程序的實現
package cn.hxex.hibernate.lock;
import java.net.URL;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.LockMode;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
public class LockMain {
??? private static Log log = LogFactory.getLog( LockMain.class );
??? // 靜態Configuration和SessionFactory對象的實例(全局唯一的)
??? private static Configuration configuration;
??? private static SessionFactory sessionFactory;
??? static
??? {
??????? // 從默認的配置文件創建SessionFactory
??????? try
??????? {
?????????????? ?????????? URL configURL = ClassLoader.getSystemResource(
?????????????? ??????????????? "cn/hxex/hibernate/lock/hibernate.cfg.xml" );
?????????????? ?????????? // 創建默認的Configuration對象的實例
??????????? configuration = new Configuration();
??????????? // 讀取hibernate.properties或者hibernate.cfg.xml文件
??????????? configuration.configure( configURL );
??????????? // 使用靜態變量來保持SessioFactory對象的實例
??????????? sessionFactory = configuration.buildSessionFactory();
??????? }
??????? catch (Throwable ex)
??????? {
??????????? // 輸出異常信息
??????????? log.error("Building SessionFactory failed.", ex);
??????????? ex.printStackTrace();
??????????? throw new ExceptionInInitializerError(ex);
??????? }
??? }
??????
??? public static SessionFactory getSessionFactory() {
???? ???? ??????? return sessionFactory;
??? }
???
??? public void testPessimisticLock() {
????????????? SessionFactory sf = LockMain.getSessionFactory();
????????????? Session session = sf.getCurrentSession();
????????????? session.beginTransaction();
?????? ??????
????????????? Query query = session.createQuery("from User user");
????????????? query.setLockMode("user", LockMode.UPGRADE);
????????????? List users = query.list();
????????????? for( int i=0; i<users.size(); i++ ) {
???????????????????? System.out.println( users.get( i ) );
????????????? }
????????????? session.getTransaction().commit();
??? }
???
?????? public static void main(String[] args) {
?????????????
????????????? LockMain main = new LockMain();
????????????? main.testPessimisticLock();
?????? }
}
?????? 在上面的清單中,testPessimisticLock()方法就是測試悲觀鎖的方法,該方法在執行查詢之前通過Query對象的setLockMode()方法設置了訪問User對象的模式,這樣,這個程序在執行的時候就會使用以下的SQL語句:
?????? select user0_.userId as userId0_, user0_.name as name0_, user0_.age as age0_
?????? from USERINFO user0_ for update
?????? 除了Query對象外,也可以在使用Session的load()或是lock()時指定鎖模式。
?????? 除了前面所提及的兩種鎖模式外,還有三種Hibernate內部自動對數據進行加鎖的模式,但它的處理是與數據庫無關的。
?????? LockMode.WRITE:在insert或update時進行鎖定,Hibernate會在調用save()方法時自動獲得鎖。
?????? LockMode.READ:在讀取記錄時Hibernate會自動獲得鎖。
?????? LockMode.NONE:沒有鎖。
?????? 如果數據庫不支持所指定的鎖模式,Hibernate會選擇一個合適的鎖替換,而不是拋出一個異常。
樂觀鎖
?????? 樂觀鎖(Optimistic Locking)認為資料的存取很少發生同時存取的問題,因而不做數據庫層次上的鎖定。為了維護正確的數據,樂觀鎖是使用應用程序上的邏輯來實現版本控制的。
在使用樂觀鎖策略的情況下,數據不一致的情況一旦發生,有幾個解決方法,一種是先更新為主,一種是后更新為主,比較復雜的就是檢查發生變動的數據來實現,或是檢查所有屬性來實現樂觀鎖。
?????? Hibernate中通過檢查版本號來判斷數據是否已經被其他人所改動,這也是Hibernate所推薦的方式。在數據庫中加入一個version字段記錄,在讀取數據時連同版本號一同讀取,并在更新數據時比較版本號與數據庫中的版本號,如果等于數據庫中的版本號則予以更新,并遞增版本號,如果小于數據庫中的版本號就拋出異常。
?????? 下面就來在前面例子的基礎上進行Hibernate樂觀鎖的測試。
?????? 首先需要修改前面所實現的業務對象,在其中增加一個version屬性,用來記錄該對象所包含數據的版本信息,修改后的User對象如清單14.5所示。
?????? 清單14.5??? 修改后的User對象
package cn.hxex.hibernate.lock;
public class User {
?????? private String id;
?????? private Integer version; // 增加版本屬性
?????? private String name;
?????? private Integer age;
??????
?????? // 省略了getter和setter方法
?????? ……
}
?????? 然后是修改映射文件,增加version屬性的配置。在這里需要注意的是,這里的version屬性應該使用專門的<version>元素來進行配置,這樣才能使其發揮樂觀鎖的作用。如果還使用<property>元素來進行配置,那么Hibernate只會將其作為一個普通的屬性來進行處理。
修改后的映射文件如清單14.6所示。
?????? 清單14.6??? 修改后的映射文件
<?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 package="cn.hxex.hibernate.lock">
?????? <class name="User" table="USERINFO" optimistic-lock="version">
????????????? <id name="id" column="userId">
????? ?????????? <generator class="uuid.hex"/>
??? ?????? </id>
?????????????
????????????? <version name="version" column="version" type="java.lang.Integer"/>
?????????????
????????????? <property name="name" column="name" type="java.lang.String"/>
????????????? <property name="age" column="age" type="java.lang.Integer"/>
?????? </class>
</hibernate-mapping>
?????? 接下來還要進行測試主程序的修改。由于需要模擬兩個人同時修改同一個記錄的情況,所以在這里需要將主程序修改為是可以多線程執行的,然后在run()方法中,調用對User對象的修改程序。
?????? 實現后的主測試程序如清單14.7所示。
?????? 清單14.7??? 修改后的測試主程序
package cn.hxex.hibernate.lock;
import java.net.URL;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.LockMode;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;
public class LockMain extends Thread{
……
??? public void testOptimisticLock() {
??? ?????? SessionFactory sf = LockMain.getSessionFactory();
??? ?????? Session session = sf.openSession();
??? ?????? Transaction tx = session.beginTransaction();
??? ??????
??? ?????? User userV1 = (User)session.load( User.class, "1" );
??? ??????
??? ?????? // 等第二個進程執行
??? ?????? try {
???????????????????? sleep( 3000 );
????????????? } catch (InterruptedException e) {
???????????????????? e.printStackTrace();
????????????? }
??? ??????
????????????? userV1.setAge(new Integer(32));
????????????? tx.commit();
????????????? session.close();
??? }
???
??? public void run() {
??? ????????????? testOptimisticLock();
??? }
???
?????? public static void main(String[] args) {
?????????????
????????????? // LockMain main = new LockMain();
????????????? // main.testPessimisticLock();
?????????????
????????????? LockMain main1 = new LockMain();
????????????? main1.start();
????????????? LockMain main2 = new LockMain();
????????????? main2.start();
?????? }
}
?????? 最后,執行測試主程序,在控制臺中應該看到類似下面的輸出:
Hibernate: select user0_.userId as userId0_0_, user0_.version as version0_0_, user0_.name as name0_0_, user0_.age as age0_0_ from USERINFO user0_ where user0_.userId=?
Hibernate: select user0_.userId as userId0_0_, user0_.version as version0_0_, user0_.name as name0_0_, user0_.age as age0_0_ from USERINFO user0_ where user0_.userId=?
Hibernate: update USERINFO set version=?, name=?, age=? where userId=? and version=?
Hibernate: update USERINFO set version=?, name=?, age=? where userId=? and version=?
2006-10-3 21:27:20 org.hibernate.event.def.AbstractFlushingEventListener performExecutions
嚴重: Could not synchronize database state with session
org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [cn.hxex.hibernate.lock.User#1]
……
?????? 在Hibernate所執行的UPDATE語句中可以看到,version字段是作為更新的條件來執行的。對于第二個進程來說,由于數據庫中的記錄已經被第一個進程更新(更新的同時會導致version自動增加),就必然會導致第二個進程操作的失敗。Hibernate正是利用這種機制來避免兩次更新問題的出現。
posted on 2009-07-19 21:11
jadmin 閱讀(160)
評論(0) 編輯 收藏