spring的聲明事務提供了強大功能,讓我們把業務關注和非業務關注的東西又分離開了。好東西的使用,總是需要有代價的。使用聲明事務的時候,一 個不小心經常會碰到“Transaction rolled back because it has been marked as rollback-only”這個異常。有時候又常常會納悶,"我已經try-catch了,為什么還這樣呢?"
- <!-- 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傳播方式。
- 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)的操作也沒有成功。
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了。
原因是既然如此,那么在不改變代碼的情況下,依靠配置能否解決這個問題呢?使用PROPAGATION_REQUIRES_NEW吧。對于 bBo.insertB(b)開個新的事務,如果失敗了就回滾調,不影響外面的insertA不就OK了。最簡單的情況就是在 transactionInterceptor1前面,再加個攔截器transactionInterceptor2,該攔截器只針對insertB的事 務屬性進行修改。
- <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了。
- <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 或者給自動代理加上順序的話。。。結果就是喜劇了。。
- <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的調用,其效果就像
- <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的順序啊,哈哈哈。。。
文章列表