spring的聲明事務提供了強大功能,讓咱們把業務關注和非業務關注的東西又分離開了。好東西的使用,老是須要有代價的。使用聲明事務的時候,一個不當心常常會碰到「Transaction rolled back because it has been marked as rollback-only」這個異常。有時候又經常會納悶,"我已經try-catch了,爲何還這樣呢?"java
Xml代碼 mysql
<!-- 0 placeHolder --> <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <property name="locations"> <list> <value>files/pro.properties</value> </list> </property> </bean> <!-- 1 dataSource --> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver"></property> <property name="url" value="${jdbc.mysql.url}"></property> <property name="username" value="${jdbc.username}"></property> <property name="password" value="${jdbc.userpassword}"></property> </bean> <!-- 2 jdbcTemplate --> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="dataSource"></property> </bean> <!-- 3 BaseDao --> <bean id="baseDao" class="transaction.dao.BaseDao" abstract="true"> <property name="jdbcTemplate" ref="jdbcTemplate" /> </bean> <bean id="aDao" class="transaction.dao.Adao" parent="baseDao"> </bean> <bean id="bDao" class="transaction.dao.Bdao" parent="baseDao"> </bean> <!-- 4 service --> <bean id="aBo" class="transaction.bo.AboImpl"> <property name="aDao" ref="aDao" /> <property name="bBo" ref="bBo" /> </bean> <bean id="bBo" class="transaction.bo.BboImpl"> <property name="bDao" ref="bDao" /> </bean> <!-- 5 transaction --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean> <bean id="transactionInterceptor1" class="org.springframework.transaction.interceptor.TransactionInterceptor" > <property name="transactionManager" ref="transactionManager"></property> <property name="transactionAttributes"> <props> <prop key="*">PROPAGATION_REQUIRED</prop> </props> </property> </bean> <bean id="autoProxy1" class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator"> <property name="beanNames"> <list> <value>*Bo</value> </list> </property> <property name="interceptorNames"> <list> <!-- <value>transactionInterceptor2</value> --> <value>transactionInterceptor1</value> </list> </property> </bean>
這裏的聲明事務是做用於全部以Bo爲後綴的bean的全部方法上,使用REQUIRED傳播方式。spring
Java代碼 sql
public int insertA(A a){ aDao.insertA(a); B b = new B(); b.setName("bbb"); try{ bBo.insertB(b); } catch(Exception e){ System.out.println("aaa"); } return 0; }
這裏,insertA 開始一個事務,調用aDao.insertA(a)[一個簡單的數據庫操做],而後調用 bBo.insertB(b)[bo調dao,dao直接拋異常]。bBo的insertB方法,也要開始一個事務,可是這裏的傳播機制是REQUIRED。OK,和insertA 的事務合二爲一吧。由於bBo.insertB(b)會拋異常出來,這裏try-catch下,但願aDao.insertA(a)的操做可以成功。數據庫
可是現實老是殘酷的,這裏會有一個大大的 「Transaction rolled back because it has been marked as rollback-only」 ,結果你會發現aDao.insertA(a)的操做也沒有成功。post
try-catch不起做用的緣由簡單的說就是,try-catch的不是地方,你認爲你的try-catch是最接近異常拋出點了,是第一個處理的handler了。實際上,spring在更早一步就try-catch 住了,同時還設置了一些標誌位,再把catch住的異常往外拋。這個時候纔是咱們的try-catch。而"Transaction rolled back because it has been marked as rollback-only"就是由於事務在提交的時候,發現標誌位已經被設置了,不該該去提交了,而後吭哧吭哧的回滾調,再提示你已經被設置成rollback-only了。url
緣由是既然如此,那麼在不改變代碼的狀況下,依靠配置可否解決這個問題呢?使用PROPAGATION_REQUIRES_NEW吧。對於bBo.insertB(b)開個新的事務,若是失敗了就回滾調,不影響外面的insertA不就OK了。最簡單的狀況就是在transactionInterceptor1前面,再加個攔截器transactionInterceptor2,該攔截器只針對insertB的事務屬性進行修改。代理
Xml代碼 code
<bean id="autoProxy1" class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator"> <property name="beanNames"> <list> <value>*Bo</value> </list> </property> <property name="interceptorNames"> <list> <value>transactionInterceptor2</value> <value>transactionInterceptor1</value> </list> </property> </bean> <bean id="transactionInterceptor2" class="org.springframework.transaction.interceptor.TransactionInterceptor" > <property name="transactionManager" ref="transactionManager"></property> <property name="transactionAttributes"> <props> <prop key="insertB">PROPAGATION_REQUIRES_NEW</prop> </props> </property> </bean>
注意interceptorNames裏面元素的位置。先要使用transactionInterceptor2,再使用transactionInterceptor1.由於調用insertB的時候,transactionInterceptor2先開了一個新事務,然後transactionInterceptor1融合進這個事務。若是這2個攔截器的順序顛倒的話,那麼仍是會出現「Transaction rolled back because it has been marked as rollback-only」。由於,transactionInterceptor2生成事務回滾之後,仍是會把ex拋給transactionInterceptor1。這個時候,transactionInterceptor1的事務和insertA的事務是同一個。transactionInterceptor1,把標誌設置好,等到insertA真的結束的時候,由於異常被咱們的try-catch捕獲了,spring就會發現須要提交的事務具備一個已經被標記號的rollback。因此就又拋出來了。事務
可是若是系統有不少遺留的因素致使你不敢盲目的修改配置文件的話(好比事務的poincut),那麼咱們就再加一個事務proxy就OK了。
Xml代碼
<bean id="autoProxy2" class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator"> <property name="beanNames"> <list> <value>*Bo</value> </list> </property> <property name="interceptorNames"> <list> <value>transactionInterceptor2</value> <!-- <value>transactionInterceptor1</value> --> </list> </property> </bean> <bean id="autoProxy1" class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator"> <property name="beanNames"> <list> <value>*Bo</value> </list> </property> <property name="interceptorNames"> <list> <value>transactionInterceptor1</value> <!-- <value>transactionInterceptor2</value> --> </list> </property> </bean>
如上的配置仍是會帶來悲劇的「Transaction rolled back because it has been marked as rollback-only」。
可是若是咱們把 autoProxy2 放到 autoProxy1 或者給自動代理加上順序的話。。。結果就是喜劇了。。
Xml代碼
<bean id="autoProxy1" class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator"> <property name="beanNames"> <list> <value>*Bo</value> </list> </property> <property name="interceptorNames"> <list> <value>transactionInterceptor1</value> <!-- <value>transactionInterceptor2</value> --> </list> </property> </bean> <bean id="autoProxy2" class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator"> <property name="beanNames"> <list> <value>*Bo</value> </list> </property> <property name="interceptorNames"> <list> <value>transactionInterceptor2</value> <!-- <value>transactionInterceptor1</value> --> </list> </property> </bean>
形成這個緣由是由使用了2個代理的順序致使的。
在作自動代理的時候,spring會按照postBeanProcessor bean聲明的順序(若是沒有設置順序的話),來依次處理bean。若是autoProxy2 在 autoProxy1 以前,這樣transactionInterceptor2 就會更加貼近insertB的調用,其效果就像
Xml代碼
<bean id="autoProxy1" class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator"> <property name="beanNames"> <list> <value>*Bo</value> </list> </property> <property name="interceptorNames"> <list> <value>transactionInterceptor1</value> <value>transactionInterceptor2</value> </list> </property> </bean>
的配置。
看來~~~ spring 仍是要注意bean的順序啊,哈哈哈。。。