Spring事務管理----------整合學習版
作者:學無先後 達者為先
Spring提供了一流的事務管理。在Spring中可以支持聲明式事務和編程式事務。
一 spring簡介
1 Spring的事務
事務管理在應用程序中起著至關重要的作用:它是一系列任務的組成工作單元,在這個工作單元中,所有的任務必須同時執行。它們只有二種可能執行結果,要麽所有任務全部執行成功,要麽所有任務全部執行失敗。
Spring中提供了豐富的事務管理功能,它們超過了EJB並且和EJB一樣支持聲明式事務,重要的是Spring提供了一致的事務管理,它有如下優點。
(1)為不同的事務的API提供一致的編程模式
(2)提供更簡單,更易地使用的編程式事務管理
(4)整合Spring對數據訪問的抽像
2 事務的ACID特性
事務使用ACID特性來衡量事務的質量。介紹如下:
(1)原子性
事務必須是原子的,在事務結束的時候,事務中的所有任務必須全部成功完成,否則全部失敗,事務回滾到事務開始之間的狀態。
(2)一致性
事務必須保證和數據庫的一致性,即數據庫中的所有數據和現實保持一致。如果事務失敗數據必須返回到事務執行之前的狀態,反之修改數據和現實的同步。
(3)隔離性
隔離性是事務與事務之間的屏障,每個事務必須與其他事務的執行結果隔離開,直到該事務執行完畢,它保證了事務的訪問的任何數據不會受其他事務執行結果的影響。隔離性不僅僅保證多個事務不能同時修改相同數據,而且能夠保證事務操作產生的變化直到變化被提交或終止時才能對另一個事務可見,並發的事務彼此之間毫無影響。這就意味著 所有要求修改或讀取的數據已經被鎖定在事務中,直到事務完成才能釋放。大多數數據庫,例如SQL Server以及其他的RDBMS,通過使用鎖定來實現隔離,事務中涉及的各個數據項或數據集使用鎖定來防止並發訪問。
如果事務成功執行,無論系統發生任何情況,事務的持久性都必須保證事務的執行結果是永久的。
3 事務之間的缺陷
在事務處理中有違返ACID特性的3個問題:臟讀取,不可重復讀和幻讀行。如果存在多個並發事務在運行,而這種事務操作了同一個數據來完成它們的任務,就會導致3個問題的存生。要解決它們,就必須在事務之間定義合適的隔離級別。
為保證事務的完整性,必須解決事務之間可能存在的3個問題。
(1)臟讀取
當一個事務讀取了另一個事務尚未提交的更新,就叫臟讀取。在另一個事務回滾的情況下,當前事務所讀取的另一個事務的數據就是無效的。 如:事務T1更新了一行記錄,還未提交所做的修改,這個T2讀取了更新後的數據,然後T1執行回滾操作,取消剛才的修改,所以T2所讀取的行就無效,也就是臟數據。
在一個事務中執行多次同樣的查詢操作,但每次查詢的結果都不一樣,就叫做不可重復讀取,通常這種情況是由於數據在二次查詢之間被另一個並發的事務所修改。 如:事務T1讀取一行記錄,緊接著事務T2修改了T1剛剛讀取的記錄,然後T1再次查詢,發現與第一次讀取的記錄不同,這稱為不可重復讀。
(3)幻影行
這是對事務危害最小的一個問候,它類似不可重復讀取,也是一個事務的更新結果影響到另一個事務問題。但是它不僅影響另一個事務查詢結果,而且還會使查詢語句返回一些不同的記行。如:事務T1讀取一條指定where條件的語句,返回結果集。此時事務T2插入一行新記錄,恰好滿足T1的where條件。然後T1使用相同的條件再次查詢,結果集中可以看到T2插入的記錄,這條新紀錄就是幻想。
場景示例鏈接:http://blog.csdn.net/fg2006/article/details/6937413
這3個問題危害程度依次為:臟讀取最大-->不可重復讀取-->幻影行最小。
4 事務的屬性
本節主要介紹將事務策略應用到方法的屬性描述,其內容包括事務的傳播行為,事務的隔離級別,事務的只讀和超時屬性。
(1) 事務的傳播行為
傳播行為是事務應用於方法的邊界,它定義了事務的建立,暫停等行為屬性。
在Spring中共有7種,EJB CMT共6種。
*PROPAGATION_MANDATORY:
規定了方法必須在事務中運行,否則會拋出異常
*PROPAGATION_NESTED:
使方法運行在嵌套事務中,否則這個屬性和PROPAGATION_REQUIRED屬性的義相同
*PROPAGATION_NEVER
使當前方法永遠不在事務中運行,否則拋出異常
*PROPAGATION_NOT_SUPPORTED
定義為當前事務不支持的方法,在該方法運行期間正在運行的事務會被暫停
*PROPAGATION_SUPPORTS
規定當前方法支持當前事務處理,但如果沒有事務在運行就使用非事務方法執行
*PROPAGATION_REQUIRED_NEW
當前方法必須創建新的事務來運行,如果現存的事務正在運行就暫停它
*PROPAGATION_REQUIRED
規定當前的方法必須在事務中,如果沒有事務就創建一個新事務,一個新事務和方法一同開始,隨著方法的返回或拋出異常而終止
以上定義Spring在事務中的傳播行為分別對應EJB的事務CMT中的所有傳播行為,其在PROPAGATION_NESTED是Spring在CMT之外定義的事務傳播行為。
例如:
<property name="transactionAttributes">
<props>
<prop key="query*">PROPAGATION_REQUIRED,timeout_5,readOnly</prop>
<prop key="insert*">PROPAGATION_REQUIRED</prop>
<prop key="delete*">PROPAGATION_REQUIRED</prop>
</props>
</property>
(2) 事務的隔離級別
為解決事務之間的3個缺陷,必須在事務之間建立隔離關系來保證事務的完整性。
ISOLATION_DEFAULT
使用數據庫默認的隔離級別
ISOLATION_READ_UNCOMMITTED(未提交讀)
允許讀取其他並發事務還未提交的更新,會導致事務之間的3個缺陷發生,這是速度最快的一個隔離級別,但同時它的隔離級別也是最低
ISOLATION_COMMITTED(提交讀)
允許讀取其他並發事務已經提交的更新(防此臟讀),即語句提交以後即執行了COMMIT以後別的事務就能讀到這個改變. 只能讀取到已經提交的數據。Oracle等多數數據庫默認都是該級別
ISOLATION_REPEATABLE_READ (可重復讀)
除非事務自身修改了數據,否則規定事務多次重復讀取數據必須相同(防此臟讀,不可重復讀) ,即在同一個事務裏面先後執行同一個查詢語句的時候,得到的結果是一樣的.在同一個事務內的查詢都是事務開始時刻一致的,InnoDB默認級別。在SQL標準中,該隔離級別消除了不可重復讀,但是還存在幻象讀
ISOLATION_SERIALIZABLE (串行讀)
這是最高的隔離級別,它可以防此臟讀,不可重復讀和幻讀等問題,但因其侵占式的數據記錄完全鎖定,導致它影響事務的性能,成為隔離級別中最展慢的一個。 即直譯就是"序列化",意思是說這個事務執行的時候不允許別的事務並發執行. 完全串行化的讀,每次讀都需要獲得表級共享鎖,讀寫相互都會阻塞
註意:並不是所有的資源管理器都支持所有的隔離級別,可針對不同的資源管理使用以上的隔離級別。
(3) 事務的只讀屬性
在對數據庫的操作中,查詢是使用最頻繁的操作,每次執行查詢時都要從數據庫中重新讀取數據,有時多次讀取的數據都是相同的,這樣的數據操作不僅浪費了系統資源,還影響了系統 速度。對訪問量大的程序來說,節省這部分資源可以大大提 升系統速度。
如果將事務聲明為只讀的,那麽數據庫可以根據事務的特性優化事務的讀取操作。事務的只讀屬性需要配合事務的傳播行為共同設置。例如:
<prop key="query*">PROPAGATION_REQUIRED,readOnly</prop>
(4) 事務的超時屬性
這個屬性和事務的只讀屬性一樣需要搭配事務的傳播行為共同設置,它設置了事務的超時時間,事務本身可能會因某種原因很長沒有回應,在這期間事務可能鎖定了數據庫的表格,這樣會出現嚴重的性能問題。通過設置事務的超時時間,從開始執行事務起,在規定的超時時間內如果沒有事務就將它回滾。事務的超時屬性以timeout_為前綴和一個整型數字定義,例如:
<prop key="query*">PROPAGATION_REGUIRED,timeout_5,readOnly</prop>
+Exception 表示遇到該異常時,執行回滾
–Exception 表示遇到該異常時,不執行加滾
二 事務的常識知識
1 一條單獨的DML會被認為是一個事務,即使沒有使用begin transaction,end transaction
2 sqlserver默認為ReadCommitted級別,允許後兩種並發問題。
orcal默認為ReadCommitted級別,允許後兩種並發問題。
Mysql的默認隔離級別就是Repeatable read
3 事務間的隔離是用鎖來實現的
4 事務之間必須是隔離的,他們不能訪問到對方。事務必須使用事務開始時數據庫中存在的記錄集合,而不應該訪問到被別的事務修改後的集合,直到事務結束。
5 oracle中在同一個窗口,運行的sql是屬於同一個事務的,所以雖然沒有提交,但是看見了也是改後的數據。
5 MySQL4.0以後可以支持事務,但是MySql的數據表分為兩類,一類是傳統的數據表,另一類則是支持事務的數據表。支持事務的數據表分為兩種:InnoDB和BerkeleyDB
三 數據庫事務操作示例
數據庫操作註意事項:http://fkl19.blog.163.com/blog/static/45203922201412510159906/
四 spring事務配置
參考:http://www.cnblogs.com/rushoooooo/archive/2011/08/28/2155960.html
http://www.blogjava.net/robbie/archive/2009/04/05/264003.html
Spring配置文件中關於事務配置總是由三個組成部分,分別是DataSource、TransactionManager和代理機制這三部分,無論哪種配置方式,一般變化的只是代理機制這部分。
DataSource、TransactionManager這兩部分只是會根據數據訪問方式有所變化,比如使用Hibernate進行數據訪問時,DataSource實際為SessionFactory,TransactionManager的實現為HibernateTransactionManager。
結構圖:
註解方式:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd"> <context:annotation-config /> <context:component-scan base-package="com.bluesky" /> <tx:annotation-driven transaction-manager="transactionManager"/> <bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean"> <property name="configLocation" value="classpath:hibernate.cfg.xml" /> <property name="configurationClass" value="org.hibernate.cfg.AnnotationConfiguration" /> </bean> <!-- 定義事務管理器(聲明式的事務) --> <bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager"> <property name="sessionFactory" ref="sessionFactory" /> </bean> </beans>
註:此時在DAO上需加上@Transactional註解,如下:
package com.bluesky.spring.dao; import java.util.List; import org.hibernate.SessionFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.orm.hibernate3.support.HibernateDaoSupport; import org.springframework.stereotype.Component; import com.bluesky.spring.domain.User; @Transactional @Component("userDao") public class UserDaoImpl extends HibernateDaoSupport implements UserDao { public List<User> listUsers() { return this.getSession().createQuery("from User").list(); } }
@Transactional註解中常用參數說明
參 數 名 稱 |
功 能 描 述 |
readOnly |
該屬性用於設置當前事務是否為只讀事務,設置為true表示只讀,false則表示可讀寫,默認值為false。例如:@Transactional(readOnly=true) |
rollbackFor |
該屬性用於設置需要進行回滾的異常類數組,當方法中拋出指定異常數組中的異常時,則進行事務回滾。例如: 指定單一異常類:@Transactional(rollbackFor=RuntimeException.class) 指定多個異常類:@Transactional(rollbackFor={RuntimeException.class, Exception.class}) |
續表)
參 數 名 稱 |
功 能 描 述 |
rollbackForClassName |
該屬性用於設置需要進行回滾的異常類名稱數組,當方法中拋出指定異常名稱數組中的異常時,則進行事務回滾。例如: 指定單一異常類名稱:@Transactional(rollbackForClassName="RuntimeException") 指定多個異常類名稱:@Transactional(rollbackForClassName={"RuntimeException","Exception"}) |
noRollbackFor |
該屬性用於設置不需要進行回滾的異常類數組,當方法中拋出指定異常數組中的異常時,不進行事務回滾。例如: 指定單一異常類:@Transactional(noRollbackFor=RuntimeException.class) 指定多個異常類:@Transactional(noRollbackFor={RuntimeException.class, Exception.class}) |
noRollbackForClassName |
該屬性用於設置不需要進行回滾的異常類名稱數組,當方法中拋出指定異常名稱數組中的異常時,不進行事務回滾。例如: 指定單一異常類名稱:@Transactional(noRollbackForClassName="RuntimeException") 指定多個異常類名稱: @Transactional(noRollbackForClassName={"RuntimeException","Exception"}) |
propagation |
該屬性用於設置事務的傳播行為,具體取值可參考表6-7。 例如:@Transactional(propagation=Propagation.NOT_SUPPORTED,readOnly=true) |
isolation |
該屬性用於設置底層數據庫的事務隔離級別,事務隔離級別用於處理多事務並發的情況,通常使用數據庫的默認隔離級別即可,基本不需要進行設置 |
timeout |
該屬性用於設置事務的超時秒數,默認值為-1表示永不超時 |
註意的幾點:
註:
(1)@Transactional(propagation=Propagation.REQUIRED)
@Transactional(timeout=30) //默認是30秒
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
(2) @Transactional 只能被應用到public方法上, 對於其它非public的方法,如果標記了@Transactional也不會報錯,但方法沒有事務功能.
(3)用 spring 事務管理器,由spring來負責數據庫的打開,提交,回滾.默認遇到運行期例外(throw new RuntimeException("註釋");)會回滾,即遇到不受檢查(unchecked)的例外時回滾;而遇到需要捕獲的例外(throw new Exception("註釋");)不會回滾,即遇到受檢查的例外(就是非運行時拋出的異常,編譯器會檢查到的異常叫受檢查例外或說受檢查異常)時,需我們指定方式來讓事務回滾 要想所有異常都回滾,要加上 @Transactional( rollbackFor={Exception.class,其它異常}) .如果讓unchecked例外不回滾: @Transactional(notRollbackFor=RunTimeException.class) 如下:
@Transactional(rollbackFor=Exception.class) //指定回滾,遇到異常Exception時回滾 public void methodName() { throw new Exception("註釋"); } @Transactional(noRollbackFor=Exception.class)//指定不回滾,遇到運行期例外(throw new RuntimeException("註釋");)會回滾 public ItimDaoImpl getItemDaoImpl() { throw new RuntimeException("註釋"); }
(4)@Transactional 註解應該只被應用到 public 可見度的方法上。 如果你在 protected、private 或者 package-visible 的方法上使用 @Transactional 註解,它也不會報錯, 但是這個被註解的方法將不會展示已配置的事務設置。
(5)@Transactional 註解可以被應用於接口定義和接口方法、類定義和類的 public 方法上。然而,請註意僅僅 @Transactional 註解的出現不足於開啟事務行為,它僅僅 是一種元數據,能夠被可以識別 @Transactional 註解和上述的配置適當的具有事務行為的beans所使用。上面的例子中,其實正是 <tx:annotation-driven/>元素的出現 開啟 了事務行為。
(6)Spring團隊的建議是你在具體的類(或類的方法)上使用 @Transactional 註解,而不要使用在類所要實現的任何接口上。你當然可以在接口上使用 @Transactional 註解,但是這將只能當你設置了基於接口的代理時它才生效。因為註解是 不能繼承 的,這就意味著如果你正在使用基於類的代理時,那麽事務的設置將不能被基於類的代理所識別,而且對象也將不會被事務代理所包裝(將被確認為嚴重的)。因 此,請接受Spring團隊的建議並且在具體的類上使用 @Transactional 註解。
Spring事務管理----------整合學習版