天天看點

Spring事務管理實作方式:程式設計式事務和聲明式事務

1.程式設計式事務:編碼方式實作事務管理(代碼示範為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方法來自動享受事務管理。

TransactionTemplate模闆類使用的回調接口:

  1. TransactionCallback:通過實作該接口的“T doInTransaction(TransactionStatus

    status) ”方法來定義需要事務管理的操作代碼;

  2. TransactionCallbackWithoutResult:繼承TransactionCallback接口,提供“void

    doInTransactionWithoutResult(TransactionStatus

    status)”便利接口用于友善那些不需要傳回值的事務操作代碼。

還是以測試類方式展示如何實作

@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);
}
           

2.聲明式事務

程式設計式事務每次實作都要單獨實作,但業務量大功能複雜時,使用程式設計式事務無疑是痛苦的,而聲明式事務不同,聲明式事務屬于無侵入式,不會影響業務邏輯的實作。和程式設計式事務相比,聲明式事務唯一不足地方是,後者的最細粒度隻能作用到方法級别,無法做到像程式設計式事務那樣可以作用到代碼塊級别。但是即便有這樣的需求,也存在很多變通的方法,比如,可以将需要進行事務管理的代碼塊獨立為方法等等。

聲明式事務管理建立在AOP之上的。其本質是對方法前後進行攔截,然後在目标方法開始之前建立或者加入一個事務,在執行完目标方法之後根據執行情況送出或者復原事務。

聲明式事務實作方式主要有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>
           

注意點:

  1. 事務復原異常隻能為RuntimeException異常,而CheckedException異常不復原,捕獲異常不抛出也不會復原,但可以強制事務復原: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提供的@Transactional注解事務管理,内部同樣是利用環繞通知TransactionInterceptor實作事務的開啟及關閉。

繼續閱讀