Spring事務管理的實現方式之編程式事務與聲明式事務詳解

原創說明:本博文爲原創做品,絕非他處轉載,轉載請聯繫博主

1.上篇文章講解了Spring事務的傳播級別與隔離級別,以及分佈式事務的簡單配置,點擊回看上篇文章

2.編程式事務:編碼方式實現事務管理(代碼演示爲JDBC事務管理)

Spring實現編程式事務,依賴於2大類,分別是上篇文章提到的PlatformTransactionManager,與模版類TransactionTemplate(推薦使用)。下面分別詳細介紹Spring是如何經過該類實現事務管理。
1)PlatformTransactionManager,上篇文章已經詳情解說了該類所擁有的方法,不記得能夠回看上篇文章。
事務管理器配置
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
	<property name="jdbcUrl" value="${db.jdbcUrl}" />
	<property name="user" value="${user}" />
	<property name="password" value="${password}" />
	<property name="driverClass" value="${db.driverClass}" />
	 <!--鏈接池中保留的最小鏈接數。 --> 
     <property name="minPoolSize"> 
         <value>5</value> 
     </property> 
     <!--鏈接池中保留的最大鏈接數。Default: 15 --> 
     <property name="maxPoolSize"> 
         <value>30</value> 
     </property> 
     <!--初始化時獲取的鏈接數,取值應在minPoolSize與maxPoolSize之間。Default: 3 --> 
     <property name="initialPoolSize"> 
         <value>10</value> 
     </property> 
     <!--最大空閒時間,60秒內未使用則鏈接被丟棄。若爲0則永不丟棄。Default: 0 --> 
     <property name="maxIdleTime"> 
         <value>60</value> 
     </property> 
     <!--當鏈接池中的鏈接耗盡的時候c3p0一次同時獲取的鏈接數。Default: 3 --> 
     <property name="acquireIncrement"> 
         <value>5</value> 
     </property> 
     <!--JDBC的標準參數,用以控制數據源內加載的PreparedStatements數量。但因爲預緩存的statements 屬於單個connection而不是整個鏈接池。因此設置這個參數須要考慮到多方面的因素。  若是maxStatements與maxStatementsPerConnection均爲0,則緩存被關閉。Default: 0 --> 
     <property name="maxStatements"> 
         <value>0</value> 
     </property> 
     <!--每60秒檢查全部鏈接池中的空閒鏈接。Default: 0 --> 
     <property name="idleConnectionTestPeriod"> 
         <value>60</value> 
     </property> 
     <!--定義在從數據庫獲取新鏈接失敗後重復嘗試的次數。Default: 30 --> 
     <property name="acquireRetryAttempts"> 
         <value>30</value> 
     </property> 
     <!--獲取鏈接失敗將會引發全部等待鏈接池來獲取鏈接的線程拋出異常。可是數據源仍有效 保留,並在下次調用getConnection()的時候繼續嘗試獲取鏈接。若是設爲true,那麼在嘗試獲取鏈接失敗後該數據源將申明已斷開並永久關閉。Default: false --> 
     <property name="breakAfterAcquireFailure"> 
         <value>true</value> 
     </property> 
     <!--因性能消耗大請只在須要的時候使用它。若是設爲true那麼在每一個connection提交的 時候都將校驗其有效性。建議使用idleConnectionTestPeriod或automaticTestTable等方法來提高鏈接測試的性能。Default: false --> 
     <property name="testConnectionOnCheckout"> 
         <value>false</value> 
     </property> 
</bean>
<!--DataSourceTransactionManager位於org.springframework.jdbc.datasource包下,數據源事務管理類,提供對單個javax.sql.DataSource數據源的事務管理,主要用於JDBC,Mybatis框架事務管理。 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
	<property name="dataSource" ref="dataSource" />
</bean>
業務中使用代碼(以測試類展現)
import java.util.Map;
import javax.annotation.Resource;
import javax.sql.DataSource;
import org.apache.log4j.Logger;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "classpath:spring-public.xml" })
public class test {
	@Resource
	private PlatformTransactionManager txManager;
	@Resource
	private  DataSource dataSource;
	private static JdbcTemplate jdbcTemplate;
	Logger logger=Logger.getLogger(test.class);
    private static final String INSERT_SQL = "insert into testtranstation(sd) values(?)";
    private static final String COUNT_SQL = "select count(*) from testtranstation";
	@Test
	public void testdelivery(){
		//定義事務隔離級別,傳播行爲,
	    DefaultTransactionDefinition def = new DefaultTransactionDefinition();  
	    def.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);  
	    def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);  
	    //事務狀態類,經過PlatformTransactionManager的getTransaction方法根據事務定義獲取;獲取事務狀態後,Spring根據傳播行爲來決定如何開啓事務
	    TransactionStatus status = txManager.getTransaction(def);  
	    jdbcTemplate = new JdbcTemplate(dataSource);
	    int i = jdbcTemplate.queryForInt(COUNT_SQL);  
	    System.out.println("表中記錄總數:"+i);
	    try {  
	        jdbcTemplate.update(INSERT_SQL, "1");  
	        txManager.commit(status);  //提交status中綁定的事務
	    } catch (RuntimeException e) {  
	        txManager.rollback(status);  //回滾
	    }  
	    i = jdbcTemplate.queryForInt(COUNT_SQL);  
	    System.out.println("表中記錄總數:"+i);
	}
}

2)使用TransactionTemplate,該類繼承了接口DefaultTransactionDefinition,用於簡化事務管理,事務管理由模板類定義,主要是經過TransactionCallback回調接口或TransactionCallbackWithoutResult回調接口指定,經過調用模板類的參數類型爲TransactionCallback或TransactionCallbackWithoutResult的execute方法來自動享受事務管理。
html

TransactionTemplate模板類使用的回調接口: java

  • TransactionCallback:經過實現該接口的「T doInTransaction(TransactionStatus status) 」方法來定義須要事務管理的操做代碼;
  • TransactionCallbackWithoutResult:繼承TransactionCallback接口,提供「void doInTransactionWithoutResult(TransactionStatus status)」便利接口用於方便那些不須要返回值的事務操做代碼。

仍是以測試類方式展現如何實現spring

@Test
public void testTransactionTemplate(){
	jdbcTemplate = new JdbcTemplate(dataSource);
    int i = jdbcTemplate.queryForInt(COUNT_SQL);  
    System.out.println("表中記錄總數:"+i);
	//構造函數初始化TransactionTemplate
	TransactionTemplate template = new TransactionTemplate(txManager);
	template.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);  
	//重寫execute方法實現事務管理
	template.execute(new TransactionCallbackWithoutResult() {
		@Override
		protected void doInTransactionWithoutResult(TransactionStatus status) {
			jdbcTemplate.update(INSERT_SQL, "餓死");   //字段sd爲int型,因此插入確定失敗報異常,自動回滾,表明TransactionTemplate自動管理事務
		}}
	);
	i = jdbcTemplate.queryForInt(COUNT_SQL);  
    System.out.println("表中記錄總數:"+i);
}

3.聲明式事務:可知編程式事務每次實現都要單獨實現,但業務量大功能複雜時,使用編程式事務無疑是痛苦的,而聲明式事務不一樣,聲明式事務屬於無侵入式,不會影響業務邏輯的實現。

聲明式事務實現方式主要有2種,一種爲經過使用Spring的<tx:advice>定義事務通知與AOP相關配置實現,另爲一種經過@Transactional實現事務管理實現,下面詳細說明2種方法如何配置,已經相關注意點
1)方式一,配置文件以下
<!-- 
<tx:advice>定義事務通知,用於指定事務屬性,其中「transaction-manager」屬性指定事務管理器,並經過<tx:attributes>指定具體須要攔截的方法
	<tx:method>攔截方法,其中參數有:
	name:方法名稱,將匹配的方法注入事務管理,可用通配符
	propagation:事務傳播行爲,
	isolation:事務隔離級別定義;默認爲「DEFAULT」
	timeout:事務超時時間設置,單位爲秒,默認-1,表示事務超時將依賴於底層事務系統;
	read-only:事務只讀設置,默認爲false,表示不是隻讀;
    rollback-for:須要觸發回滾的異常定義,可定義多個,以「,」分割,默認任何RuntimeException都將致使事務回滾,而任何Checked Exception將不致使事務回滾;
    no-rollback-for:不被觸發進行回滾的 Exception(s);可定義多個,以「,」分割;
 -->
<tx:advice id="advice" transaction-manager="transactionManager">
	<tx:attributes>
	    <!-- 攔截save開頭的方法,事務傳播行爲爲:REQUIRED:必需要有事務, 若是沒有就在上下文建立一個 -->
		<tx:method name="save*" propagation="REQUIRED" isolation="READ_COMMITTED" timeout="" read-only="false" no-rollback-for="" rollback-for=""/>
		<!-- 支持,若是有就有,沒有就沒有 -->
		<tx:method name="*" propagation="SUPPORTS"/>
	</tx:attributes>
</tx:advice>
<!-- 定義切入點,expression爲切人點表達式,以下是指定impl包下的全部方法,具體以自身實際要求自定義  -->
<aop:config>
    <aop:pointcut expression="execution(* com.kaizhi.*.service.impl.*.*(..))" id="pointcut"/>
    <!--<aop:advisor>定義切入點,與通知,把tx與aop的配置關聯,纔是完整的聲明事務配置 -->
    <aop:advisor advice-ref="advice" pointcut-ref="pointcut"/>
</aop:config>
關於事務傳播行爲與隔離級別,可參考 http://blog.csdn.net/liaohaojian/article/details/68488150
注意點:
  1. 事務回滾異常只能爲RuntimeException異常,而Checked Exception異常不回滾,捕獲異常不拋出也不會回滾,但能夠強制事務回滾:TransactionAspectSupport.currentTransactionStatus().isRollbackOnly();
  2. 解決「自我調用」而致使的不能設置正確的事務屬性問題,可參考http://www.iteye.com/topic/1122740
2)方式二經過@Transactional實現事務管理
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">   
     <property name="dataSource" ref="dataSource"/>
</bean>    
<tx:annotation-driven transaction-manager="txManager"/> //開啓事務註解
@Transactional(propagation=Propagation.REQUIRED,isolation=Isolation.READ_COMMITTED),具體參數跟上面<tx:method>中同樣
Spring提供的@Transaction註解事務管理,內部一樣是利用環繞通知TransactionInterceptor實現事務的開啓及關閉。
使用@Transactional注意點:
  1. 若是在接口、實現類或方法上都指定了@Transactional 註解,則優先級順序爲方法>實現類>接口;
  2. 建議只在實現類或實現類的方法上使用@Transactional,而不要在接口上使用,這是由於若是使用JDK代理機制(基於接口的代理)是沒問題;而使用使用CGLIB代理(繼承)機制時就會遇到問題,由於其使用基於類的代理而不是接口,這是由於接口上的@Transactional註解是「不能繼承的」;

本人另外一博文【http://blog.csdn.net/liaohaojian/article/details/70139151】歡迎各位瀏覽sql

相關文章
相關標籤/搜索