spring中JDBC 聲明式事務管理

方式一:使用org.springframework.transaction.interceptor.TransactionProxyFactoryBean
applicationContext.xml

測試代碼:

當updateUser中發生異常的時候,則兩條記錄都不會被插入。此方式實現的缺點,針對每個需要進行事務管理的bean都需要定義
TransactionProxyFactoryBean,造成較多的冗餘代碼。


方式二:
採用TransactionInterceptor通知和BeanNameAutoProxyCreator自動代理
applicationContext.xml

測試代碼:

聲明式事務又有三種實現方法:

1 (第一種) 最早的方法,用TransactionProxyFactoryBean,他是一個有AOP代理功能的FactoryBean.他返回的對象有事務.

還要在spring的配置文件XML中配置,比較麻煩,不詳細說.

Xml代碼
  1. <!-- 事務測試DAO -->  
  2. <bean id="go_TestPOAO" class="pic.dao.transaction_test.TestPOAOImpl" parent="go_POAOBase"></bean>      
  3.        
  4. <!-- 事務測試DAO 聲明式事務管理 -->  
  5. <bean id="go_TestPOAOProxy" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">     
  6.         <property name="proxyInterfaces">     
  7.             <list>      
  8.                 <value>pic.dao.transaction_test.TestPOAO</value>     
  9.             </list>     
  10.         </property>     
  11.         <property name="target" ref="go_TestPOAO"/>      
  12.         <property name="transactionManager" ref="transactionManager"/>     
  13.         <property name="transactionAttributes">     
  14.             <props>     
  15.                 <prop key="insert*">PROPAGATION_REQUIRED</prop>     
  16.             </props>     
  17.         </property>     
  18. </bean>   

2 (第二種) 使用<tx:>來實現聲明式事務 ,也要在spring的配置文件XML中配置,比較麻煩,不詳細說.

Xml代碼
  1. <tx:advice id="">  
  2. .....   
  3. </tx:advice>  
  4.   
  5. <aop:config>  
  6. .....   
  7. </aop:config>  

 

 

3 (第三種) 這個方法方便,使用註解來實現聲明式事務, 下面詳細說說這個方法:

 

第一步:引入<tx:>命名空間 ,在spring的配置文件中修改, beans根元素裏多了三行,如下

Xml代碼
  1. <?xml version="1.0" encoding="UTF-8"?>  
  2. <beans  
  3.     xmlns="http://www.springframework.org/schema/beans"  
  4.     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
  5.     xmlns:tx="http://www.springframework.org/schema/tx"  
  6.     xsi:schemaLocation="http://www.springframework.org/schema/beans   
  7.      http://www.springframework.org/schema/beans/spring-beans-2.0.xsd   
  8.      http://www.springframework.org/schema/tx   
  9.      http://www.springframework.org/schema/tx/spring-tx-2.0.xsd">  

 

第二步:在spring的配置文件中修改,將所有具有@Transactional 註解的bean自動配置爲聲明式事務支持

Java代碼
  1. <!--JDBC事務管理器,根據你的情況使用不同的事務管理器,如果工程中有Hibernate,就用Hibernate的事務管理器 -->     
  2. <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">   
  3.          <property name="dataSource">   
  4.              <ref local="dataSource"/>   
  5.          </property>   
  6. </bean>      
  7.            
  8. <!-- 用註解來實現事務管理 -->   
  9. <tx:annotation-driven transaction-manager="transactionManager"/>  
<!--JDBC事務管理器,根據你的情況使用不同的事務管理器,如果工程中有Hibernate,就用Hibernate的事務管理器 --> 
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
  <property name="dataSource">
   <ref local="dataSource"/>
  </property>
</bean> 
  
<!-- 用註解來實現事務管理 -->
<tx:annotation-driven transaction-manager="transactionManager"/>

 

第三步: 在接口或類的聲明處 ,寫一個@Transactional. 要是隻的接口上寫, 接口的實現類就會繼承下來.

接口的實現類的具體方法,還可以覆蓋類聲明處的設置.

Java代碼
  1. @Transactional  
  2. public class TestPOAOImpl extends POAOBase implements TestPOAO   
  3. {      
  4.     @Transactional(isolation = Isolation.READ_COMMITTED)   
  5.     public void test1()   
  6.      {   
  7.          String sql = "INSERT INTO sy_test (NAME,AGE) VALUES('註解趙雲',30)";   
  8.          execute(sql);   
  9.   
  10.          sql = "INSERT INTO sy_test (NAME,AGE) VALUES('註解張飛',26)";   
  11.          execute(sql);   
  12.   
  13.         int a = 9 / 0; //異常   
  14.   
  15.          sql = "INSERT INTO sy_test (NAME,AGE) VALUES('註解關羽',33)";   
  16.          execute(sql);   
  17.          System.out.println("走完了");   
  18.      }   
  19. //execute() 方法略...   
  20. }  
@Transactional
public class TestPOAOImpl extends POAOBase implements TestPOAO
{ 
    @Transactional(isolation = Isolation.READ_COMMITTED)
    public void test1()
 {
  String sql = "INSERT INTO sy_test (NAME,AGE) VALUES('註解趙雲',30)";
  execute(sql);

  sql = "INSERT INTO sy_test (NAME,AGE) VALUES('註解張飛',26)";
  execute(sql);

  int a = 9 / 0; //異常

  sql = "INSERT INTO sy_test (NAME,AGE) VALUES('註解關羽',33)";
  execute(sql);
  System.out.println("走完了");
 }
//execute() 方法略...
}

 

注意的幾點:

1 @Transactional 只能被應用到public方法上, 對於其它非public的方法,如果標記了@Transactional也不會報錯,但方法沒有事務功能.

 

2 默認情況下,一個有事務方法, 遇到RuntiomeException會回滾 . 遇到 受檢查的異常不會回滾 的. 要想所有異常都回滾,要加上 @Transactional( rollbackFor={Exception.class,其它異常}) .

 

 

 

@Transactional 的所有可選屬性如下:

 

屬性 類型 默認值 說明
propagation Propagation枚舉 REQUIRED 事務傳播屬性 (下有說明)
isolation isolation枚舉 DEFAULT 事務隔離級別 (另有說明)
readOnly boolean false 是否只讀
timeout int -1 超時(秒)
rollbackFor Class[] {} 需要回滾的異常類
rollbackForClassName String[] {} 需要回滾的異常類名
noRollbackFor Class[] {} 不需要回滾的異常類
noRollbackForClassName String[] {} 不需要回滾的異常類名

 

 

事務的隔離級別 有如下可選:

可以去看spring源碼 : org.springframework.transaction.annotation.Isolation

DEFAULT採用數據庫默認隔離級別

READ_UNCOMMITTED

READ_COMMITTED

REPEATABLE_READ

SERIALIZABLE

數據庫提供了四種事務隔離級別, 不同的隔離級別採用不同的鎖類開來實現.

在四種隔離級別中, Serializable的級別最高, Read Uncommited級別最低.

大多數數據庫的默認隔離級別爲: Read Commited,如Sql Server , Oracle.

少數數據庫默認的隔離級別爲Repeatable Read, 如MySQL InnoDB存儲引擎

即使是最低的級別,也不會出現 第一類 丟失 更新問題 .

  • Read Uncommited :讀未提交數據( 會出現髒讀,不可重複讀,幻讀 ,避免了 第一類丟失 更新 )
  • Read Commited :讀已提交的數據(會出現不可重複讀,幻讀)
  • Repeatable Read :可重複讀(會出現幻讀)
  • Serializable :串行化

 

丟失 更新 :
當兩個或多個事務選擇同一行,然後基於最初選定的值更新該行時,會發生丟失更新問題。每個事務都不知道其它事務的存在。最後的更新將重寫由其它事務所做的更新,這將導致數據丟失。   
例:
事務A和事務B同時修改某行的值,
1.事務A將數值改爲1並提交
2.事務B將數值改爲2並提交。
這時數據的值爲2,事務A所做的更新將會丟失。
解決辦法:對行加鎖,只允許併發一個更新事務。

 

髒讀: 一個事務讀到另一個事務未提交的更新數據

例:

1.Mary的原工資爲1000, 財務人員將Mary的工資改爲了8000(但未提交事務)        
2.Mary讀取自己的工資 ,發現自己的工資變爲了8000,歡天喜地!
3.而財務發現操作有誤,回滾了事務,Mary的工資又變爲了1000, 像這樣,Mary記取的工資數8000是一個髒數據。

 

不可重複讀: 在同一個事務中,多次讀取同一數據,返回的結果有所不同. 換句話說就是,後續讀取可以讀到另一個事務已提交的更新數據. 相反"可重複讀"在同一事務多次讀取數據時,能夠保證所讀數據一樣,也就是後續讀取不能讀到另一事務已提交的更新數據.

例:

1.在事務1中,Mary 讀取了自己的工資爲1000,操作並沒有完成
2.在事務2中,這時財務人員修改了Mary的工資爲2000,並提交了事務.
3.在事務1中,Mary 再次讀取自己的工資時,工資變爲了2000
解決辦法:如果只有在修改事務完全提交之後纔可以讀取數據,則可以避免該問題。

 

幻讀: 一個事務讀取到另一個事務已提交的insert數據.

例:

第一個事務對一個表中的數據進行了修改,這種修改涉及到表中的全部數據行。同時 (此時第一事務還未提交) ,第二個事務向表中插入一行新數據。這時第一個事務再去讀取表時,發現表中還有沒有修改的數據行,就好象發生了幻覺一樣。

 

事務的傳播屬性 ,有如下可選

可以去看spring源碼 : org.springframework.transaction.annotation.Propagation

 

REQUIRED 業務方法需要在一個事務中運行,如果方法運行時,已處在一個事務中,那麼就加入該事務,否則自己創建一個新的事務.這是spring默認的傳播行爲.
SUPPORTS 如果業務方法在某個事務範圍內被調用,則方法成爲該事務的一部分,如果業務方法在事務範圍外被調用,則方法在沒有事務的環境下執行.
MANDATORY 只能在一個已存在事務中執行,業務方法不能發起自己的事務,如果業務方法在沒有事務的環境下調用,就拋異常
REQUIRES_NEW 業務方法總是會爲自己發起一個新的事務,如果方法已運行在一個事務中,則原有事務被掛起,新的事務被創建,直到方法結束,新事務才結束,原先的事務纔會恢復執行.
NOT_SUPPORTED 聲明方法需要事務,如果方法沒有關聯到一個事務,容器不會爲它開啓事務.如果方法在一個事務中被調用,該事務會被掛起,在方法調用結束後,原先的事務便會恢復執行.
NEVER 聲明方法絕對不能在事務範圍內執行,如果方法在某個事務範圍內執行,容器就拋異常.只有沒關聯到事務,才正常執行.
NESTED 如果一個活動的事務存在,則運行在一個嵌套的事務中.如果沒有活動的事務,則按REQUIRED屬性執行.它使用了一個單獨的事務, 這個事務擁有多個可以回滾的保證點.內部事務回滾不會對外部事務造成影響, 它只對DataSourceTransactionManager 事務管理器起效.
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章