1.上篇文章講解了Spring事務(wù)的傳播級(jí)別與隔離級(jí)別讨勤,以及分布式事務(wù)的簡(jiǎn)單配置箭跳,點(diǎn)擊回看上篇文章
2.編程式事務(wù):編碼方式實(shí)現(xiàn)事務(wù)管理(代碼演示為JDBC事務(wù)管理)
Spring實(shí)現(xiàn)編程式事務(wù),依賴于2大類潭千,分別是上篇文章提到的PlatformTransactionManager谱姓,與模版類TransactionTemplate(推薦使用)。下面分別詳細(xì)介紹Spring是如何通過(guò)該類實(shí)現(xiàn)事務(wù)管理刨晴。
1)PlatformTransactionManager屉来,上篇文章已經(jīng)詳情解說(shuō)了該類所擁有的方法路翻,不記得可以回看上篇文章。
事務(wù)管理器配置
<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}" />
<!--連接池中保留的最小連接數(shù)奶躯。 -->
<property name="minPoolSize">
<value>5</value>
</property>
<!--連接池中保留的最大連接數(shù)。Default: 15 -->
<property name="maxPoolSize">
<value>30</value>
</property>
<!--初始化時(shí)獲取的連接數(shù)亿驾,取值應(yīng)在minPoolSize與maxPoolSize之間嘹黔。Default: 3 -->
<property name="initialPoolSize">
<value>10</value>
</property>
<!--最大空閑時(shí)間,60秒內(nèi)未使用則連接被丟棄。若為0則永不丟棄莫瞬。Default: 0 -->
<property name="maxIdleTime">
<value>60</value>
</property>
<!--當(dāng)連接池中的連接耗盡的時(shí)候c3p0一次同時(shí)獲取的連接數(shù)儡蔓。Default: 3 -->
<property name="acquireIncrement">
<value>5</value>
</property>
<!--JDBC的標(biāo)準(zhǔn)參數(shù),用以控制數(shù)據(jù)源內(nèi)加載的PreparedStatements數(shù)量疼邀。但由于預(yù)緩存的statements 屬于單個(gè)connection而不是整個(gè)連接池喂江。所以設(shè)置這個(gè)參數(shù)需要考慮到多方面的因素。 如果maxStatements與maxStatementsPerConnection均為0旁振,則緩存被關(guān)閉获询。Default: 0 -->
<property name="maxStatements">
<value>0</value>
</property>
<!--每60秒檢查所有連接池中的空閑連接。Default: 0 -->
<property name="idleConnectionTestPeriod">
<value>60</value>
</property>
<!--定義在從數(shù)據(jù)庫(kù)獲取新連接失敗后重復(fù)嘗試的次數(shù)拐袜。Default: 30 -->
<property name="acquireRetryAttempts">
<value>30</value>
</property>
<!--獲取連接失敗將會(huì)引起所有等待連接池來(lái)獲取連接的線程拋出異常吉嚣。但是數(shù)據(jù)源仍有效 保留,并在下次調(diào)用getConnection()的時(shí)候繼續(xù)嘗試獲取連接蹬铺。如果設(shè)為true尝哆,那么在嘗試獲取連接失敗后該數(shù)據(jù)源將申明已斷開(kāi)并永久關(guān)閉。Default: false -->
<property name="breakAfterAcquireFailure">
<value>true</value>
</property>
<!--因性能消耗大請(qǐng)只在需要的時(shí)候使用它甜攀。如果設(shè)為true那么在每個(gè)connection提交的 時(shí)候都將校驗(yàn)其有效性秋泄。建議使用idleConnectionTestPeriod或automaticTestTable等方法來(lái)提升連接測(cè)試的性能。Default: false -->
<property name="testConnectionOnCheckout">
<value>false</value>
</property>
</bean>
<!--DataSourceTransactionManager位于org.springframework.jdbc.datasource包下规阀,數(shù)據(jù)源事務(wù)管理類恒序,提供對(duì)單個(gè)javax.sql.DataSource數(shù)據(jù)源的事務(wù)管理,主要用于JDBC谁撼,Mybatis框架事務(wù)管理奸焙。 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
業(yè)務(wù)中使用代碼(以測(cè)試類展示)
業(yè)務(wù)中使用代碼(以測(cè)試類展示)
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(){
//定義事務(wù)隔離級(jí)別,傳播行為彤敛,
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
def.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
//事務(wù)狀態(tài)類与帆,通過(guò)PlatformTransactionManager的getTransaction方法根據(jù)事務(wù)定義獲取墨榄;獲取事務(wù)狀態(tài)后玄糟,Spring根據(jù)傳播行為來(lái)決定如何開(kāi)啟事務(wù)
TransactionStatus status = txManager.getTransaction(def);
jdbcTemplate = new JdbcTemplate(dataSource);
int i = jdbcTemplate.queryForInt(COUNT_SQL);
System.out.println("表中記錄總數(shù):"+i);
try {
jdbcTemplate.update(INSERT_SQL, "1");
txManager.commit(status); //提交status中綁定的事務(wù)
} catch (RuntimeException e) {
txManager.rollback(status); //回滾
}
i = jdbcTemplate.queryForInt(COUNT_SQL);
System.out.println("表中記錄總數(shù):"+i);
}
}
2)使用TransactionTemplate,該類繼承了接口DefaultTransactionDefinition袄秩,用于簡(jiǎn)化事務(wù)管理阵翎,事務(wù)管理由模板類定義逢并,主要是通過(guò)TransactionCallback回調(diào)接口或TransactionCallbackWithoutResult回調(diào)接口指定,通過(guò)調(diào)用模板類的參數(shù)類型為TransactionCallback或TransactionCallbackWithoutResult的execute方法來(lái)自動(dòng)享受事務(wù)管理郭卫。
TransactionTemplate模板類使用的回調(diào)接口:
- TransactionCallback:通過(guò)實(shí)現(xiàn)該接口的“T doInTransaction(TransactionStatus status) ”方法來(lái)定義需要事務(wù)管理的操作代碼砍聊;
- TransactionCallbackWithoutResult:繼承TransactionCallback接口,提供“void doInTransactionWithoutResult(TransactionStatus status)”便利接口用于方便那些不需要返回值的事務(wù)操作代碼贰军。
還是以測(cè)試類方式展示如何實(shí)現(xiàn)
@Test
public void testTransactionTemplate(){
jdbcTemplate = new JdbcTemplate(dataSource);
int i = jdbcTemplate.queryForInt(COUNT_SQL);
System.out.println("表中記錄總數(shù):"+i);
//構(gòu)造函數(shù)初始化TransactionTemplate
TransactionTemplate template = new TransactionTemplate(txManager);
template.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);
//重寫execute方法實(shí)現(xiàn)事務(wù)管理
template.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
jdbcTemplate.update(INSERT_SQL, "餓死"); //字段sd為int型玻蝌,所以插入肯定失敗報(bào)異常,自動(dòng)回滾词疼,代表TransactionTemplate自動(dòng)管理事務(wù)
}}
);
i = jdbcTemplate.queryForInt(COUNT_SQL);
System.out.println("表中記錄總數(shù):"+i);
}
3.聲明式事務(wù):可知編程式事務(wù)每次實(shí)現(xiàn)都要單獨(dú)實(shí)現(xiàn)俯树,但業(yè)務(wù)量大功能復(fù)雜時(shí),使用編程式事務(wù)無(wú)疑是痛苦的贰盗,而聲明式事務(wù)不同许饿,聲明式事務(wù)屬于無(wú)侵入式,不會(huì)影響業(yè)務(wù)邏輯的實(shí)現(xiàn)舵盈。
聲明式事務(wù)實(shí)現(xiàn)方式主要有2種陋率,一種為通過(guò)使用Spring的<tx:advice>定義事務(wù)通知與AOP相關(guān)配置實(shí)現(xiàn),另為一種通過(guò)@Transactional實(shí)現(xiàn)事務(wù)管理實(shí)現(xiàn)秽晚,下面詳細(xì)說(shuō)明2種方法如何配置翘贮,已經(jīng)相關(guān)注意點(diǎn)
1)方式一,配置文件如下
<!--
<tx:advice>定義事務(wù)通知爆惧,用于指定事務(wù)屬性狸页,其中“transaction-manager”屬性指定事務(wù)管理器,并通過(guò)<tx:attributes>指定具體需要攔截的方法
<tx:method>攔截方法扯再,其中參數(shù)有:
name:方法名稱芍耘,將匹配的方法注入事務(wù)管理,可用通配符
propagation:事務(wù)傳播行為熄阻,
isolation:事務(wù)隔離級(jí)別定義斋竞;默認(rèn)為“DEFAULT”
timeout:事務(wù)超時(shí)時(shí)間設(shè)置,單位為秒秃殉,默認(rèn)-1坝初,表示事務(wù)超時(shí)將依賴于底層事務(wù)系統(tǒng);
read-only:事務(wù)只讀設(shè)置钾军,默認(rèn)為false鳄袍,表示不是只讀;
rollback-for:需要觸發(fā)回滾的異常定義吏恭,可定義多個(gè)拗小,以“,”分割樱哼,默認(rèn)任何RuntimeException都將導(dǎo)致事務(wù)回滾哀九,而任何Checked Exception將不導(dǎo)致事務(wù)回滾剿配;
no-rollback-for:不被觸發(fā)進(jìn)行回滾的 Exception(s);可定義多個(gè)阅束,以“呼胚,”分割;
-->
<tx:advice id="advice" transaction-manager="transactionManager">
<tx:attributes>
<!-- 攔截save開(kāi)頭的方法息裸,事務(wù)傳播行為為:REQUIRED:必須要有事務(wù), 如果沒(méi)有就在上下文創(chuàng)建一個(gè) -->
<tx:method name="save*" propagation="REQUIRED" isolation="READ_COMMITTED" timeout="" read-only="false" no-rollback-for="" rollback-for=""/>
<!-- 支持,如果有就有,沒(méi)有就沒(méi)有 -->
<tx:method name="*" propagation="SUPPORTS"/>
</tx:attributes>
</tx:advice>
<!-- 定義切入點(diǎn)蝇更,expression為切人點(diǎn)表達(dá)式,如下是指定impl包下的所有方法界牡,具體以自身實(shí)際要求自定義 -->
<aop:config>
<aop:pointcut expression="execution(* com.kaizhi.*.service.impl.*.*(..))" id="pointcut"/>
<!--<aop:advisor>定義切入點(diǎn)簿寂,與通知漾抬,把tx與aop的配置關(guān)聯(lián),才是完整的聲明事務(wù)配置 -->
<aop:advisor advice-ref="advice" pointcut-ref="pointcut"/>
關(guān)于事務(wù)傳播行為與隔離級(jí)別宿亡,可參考http://blog.csdn.net/liaohaojian/article/details/68488150
注意點(diǎn):
- 事務(wù)回滾異常只能為RuntimeException異常,而Checked Exception異常不回滾纳令,捕獲異常不拋出也不會(huì)回滾挽荠,但可以強(qiáng)制事務(wù)回滾:TransactionAspectSupport.currentTransactionStatus().isRollbackOnly();
- 解決“自我調(diào)用”而導(dǎo)致的不能設(shè)置正確的事務(wù)屬性問(wèn)題,可參考http://www.iteye.com/topic/1122740
2)方式二通過(guò)@Transactional實(shí)現(xiàn)事務(wù)管理
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<tx:annotation-driven transaction-manager="txManager"/> //開(kāi)啟事務(wù)注解
@Transactional(propagation=Propagation.REQUIRED,isolation=Isolation.READ_COMMITTED)平绩,具體參數(shù)跟上面<tx:method>中一樣
Spring提供的@Transaction注解事務(wù)管理圈匆,內(nèi)部同樣是利用環(huán)繞通知TransactionInterceptor實(shí)現(xiàn)事務(wù)的開(kāi)啟及關(guān)閉。
使用@Transactional注意點(diǎn):
- 如果在接口捏雌、實(shí)現(xiàn)類或方法上都指定了@Transactional 注解跃赚,則優(yōu)先級(jí)順序?yàn)榉椒?gt;實(shí)現(xiàn)類>接口;
- 建議只在實(shí)現(xiàn)類或?qū)崿F(xiàn)類的方法上使用@Transactional性湿,而不要在接口上使用纬傲,這是因?yàn)槿绻褂肑DK代理機(jī)制(基于接口的代理)是沒(méi)問(wèn)題;而使用使用CGLIB代理(繼承)機(jī)制時(shí)就會(huì)遇到問(wèn)題肤频,因?yàn)槠涫褂没陬惖拇矶皇墙涌谔纠ǎ@是因?yàn)榻涌谏系腀Transactional注解是“不能繼承的”;