Spring AOP 日誌攔截器的事務管理

weixin_34413065發表於2018-05-18

如果要在方法執行前或後或丟擲異常後加上一個自己的攔截器,或者一個環繞攔截器,在攔截器中執行一些操作,比如執行一些資料庫操作,記錄一些信 息,這些操作通過呼叫一個服務類的方法來執行,這個方法也在spring事務管理攔截器的管理之下,那麼這個記錄方法需要在另一個事務中進行,而不是與被 攔截方法在同一個事務中,不然如果被攔截方法丟擲異常需要回滾時,所作的記錄也會被回滾,當然有時候確實需要同時回滾,那就要放在同一個事務中。 

這和自己的攔截器和事務管理的攔截器的執行順序有一定關係,spring事務管理攔截器是一個環繞通知,在被攔截方法執行前啟動事務,執行後完成 事務,如果自己的攔截器被spring事務管理攔截器包圍在裡面,那麼在自己的攔截器執行時,spring已經啟動了一個事務,如果你的記錄資訊方法需要 與被攔截方法同在一個事務中,將你的記錄資訊方法的事務傳播屬性設為預設的REQUIRED就可以了; 
如果你記錄資訊的方法需要單獨的一個事務環境,那就要把事務傳播屬性設為REQUIRES_NEW了,這樣spring事務管理器會新建一個事 務,並且新建一個session連線,因為一個資料庫連線不可能同時有兩個事務,記錄資訊完了提交事務並且把新建的session連線關閉,自己的攔截器 退出後繼續執行被攔截的方法或它的事務處理。 

相反如果自己的攔截器在spring事務管理攔截器的外面,那麼記錄資訊的方法會在一個單獨的事務中執行,並提交,不管它的事務傳播屬性是 REQUIRES_NEW還是REQUIRED,因為與被攔截方法的事務處理沒有交叉,並且可以使用同一個session連線如果是 OpenSessionInViewFilter。 


所以如果記錄資訊和被攔截方法要在不同事務中執行,分別提交,那麼最好將自己的攔截器設在spring事務管理器攔截器的外面;如果需要將記錄信 息和被攔截方法在同一個事務中處理,必須將自己的攔截器被包圍在spring事務管理攔截器中,並且記錄資訊方法的事務傳播屬性為預設的 REQUIRED。 設定攔截器的執行順序可以讓攔截器處理類實現org.springframework.core.Ordered介面,在spring配置檔案的 AOP設定中設定自己的攔截器和spring事務管理攔截器的執行順序,將自己的攔截的序號排在spring事務管理的前面,就可以將該攔截器放到事務管 理攔截器的外面執行了,對於before通知方式會先於事務管理攔截器執行,對於after returning和after和after throwing通知方式會後於事務管理攔截器的執行,對於arount通知方式會包圍事務管理攔截器執行。 
下面是一個異常攔截器的例子。 
有位朋友提到在spring異常攔截器中更新資料不能夠提交,做了一下測試,測試環境基本是這樣:一個使用者登入的功能,spring對 service中的每個方法進行事務管理,在使用者檢測的service方法上同時加上一個異常攔截器,當使用者不存在或密碼不正確時使用者檢測方法會丟擲異 常,異常攔截器捕獲到該異常,同時記錄一些日誌。 

	<!-- 事務管理 -->
	<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager"> 
		<property name="sessionFactory" ref="sessionFactory"></property>
	</bean>
	
	<!-- 事務通知 -->
	<tx:advice id="txAdvice" transaction-manager="transactionManager">
		<tx:attributes>
			<tx:method name="get*" read-only="true"/>
			<tx:method name="*" propagation="REQUIRES_NEW" rollback-for="Exception"/>
		</tx:attributes>
	</tx:advice>
	
	<!-- aop代理設定 -->
	<aop:config proxy-target-class="true">
		<aop:pointcut id="txPointcut" expression="execution(* com.hbs..*Service.*(..))"/>
		<aop:pointcut id="logPointcut" expression="execution(* com.hbs.customer..*Service.*(..))"/>
		<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut" order="1"/>
		<aop:aspect id="logAspect" ref="logInterceptor" order="2" >
			<aop:after-throwing
				pointcut-ref="logPointcut" 
				method="serviceIntercept" />
		</aop:aspect>
	</aop:config>
	
	<!-- log攔截器類 -->
	<bean id="logInterceptor" class="com.hbs.eventlog.EventLogInterceptor">
		<property name="service" ref="logService"></property>
	</bean>	

service方法中的事務傳播屬性都設為要求新建事務,spring事務管理切面攔截器的order設為1,而log攔截器的order設為2,這意味 著這兩個要同時執行時,先執行事務攔截器,後執行log攔截器,由於事務管理是一個環繞通知(around),實際上是log攔截器被包圍在事務管理攔截 器中。 

從中可以看出,log異常攔截器在使用者登入的事務回滾之前截獲異常,在記錄日誌時,日誌記錄的service方法也在spring的事務管理之 下,使用者登入的事務還沒有結束,根據REQUIRES_NEW特性,spring會新開一個事務,這時原來的資料庫連線已經在一個事務中,一個連線不可能 同時有兩個事務,所以同時新建立一個session連線(雖然我使用了OpenSessionInViewFilter,並且session是單例的), 日誌記錄就在新建的事務和session中進行,完了提交,並且會把新建的session連線關閉。 
然後繼續進行被中斷的使用者登入的事務管理操作,由於拋異常spring將使用者登入的事務回滾。 
這樣能夠實現預想的功能,但是如果我去掉指定的REQUIRES_NEW,那麼log記錄的操作會繼續在使用者登入的事務中進行,最後會被一起回滾。 

如果把事務管理的order設為2,log攔截器的order設為1,也就是log攔截器在事務管理攔截器的外面,會在事務管理攔截器前後執行完了再執行log的異常攔截器。

可以看出,使用者登入的事務和日誌記錄的事務是前後兩個不相關的事務,並且在日誌記錄事務中並不需要新建session連線,而是直接用在 OpenSessionInViewFilter中建立的session。實際上這時也並不需要將propagation設為REQUIRES_NEW, 使用預設的REQUIRES也照樣能夠正常工作。 

所以應該將該異常攔截器設在事務管理攔截器的外面,即使用Order介面排在前面。

 

 

Spring中事務與aop的先後順序問題

 Spring中的事務是通過aop來實現的,當我們自己寫aop攔截的時候,會遇到跟spring的事務aop執行的先後順序問題,比如說動態切換資料來源的問題,如果事務在前,資料來源切換在後,會導致資料來源切換失效,所以就用到了Order(排序)這個關鍵字.

        我們可以通過在@AspectJ的方法中實現org.springframework.core.Ordered 這個介面來定義order的順序,order 的值越小,說明越先被執行。比如程式碼如下:

/**

 * @author HuifengWang

 * aop面向切面程式設計

 *

 */

@Component

@Aspect

public class AspectJ4DataBase implements Ordered{

     

    //攔截所有的service操作

    @Pointcut("execution( * com.hc.shop.*.service.*.*(..))")

    public void readMethod() {

    }// 匹配所有的讀取操作

     

    @Before("readMethod()")

    public void onlyReadPre(){

        DataSourceContextHolder.setDataSourceType(DataSourceType.MYSQL);

        System.out.println("資料庫切換MYSQL");

    }

    @After("readMethod()")

    public void onlyReadPast(){

        DataSourceContextHolder.setDataSourceType(DataSourceType.ORACLE);

        System.out.println("資料庫切換回ORACLE");

    }

 

    @Override

    public int getOrder() {

        // TODO Auto-generated method stub

        return 1;

    }

}

 在事務配置的地方也配置order 欄位,程式碼如下:

<!-- 註解方式配置事物 -->

<tx:annotation-driven transaction-manager="transactionManager" order="2"/>

這樣就實現了我們自己寫的aop在事務介入之前就執行了!

轉載於:https://my.oschina.net/LucasZhu/blog/1814566

相關文章