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

如果要在方法執行前或後或拋出異常後加上一個自己的攔截器,或者一個環繞攔截器,在攔截器中執行一些操作,比如執行一些數據庫操作,記錄一些信 息,這些操作通過調用一個服務類的方法來執行,這個方法也在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方法上同時加上一個異常攔截器,當用戶不存在或密碼不正確時用戶檢測方法會拋出異 常,異常攔截器捕獲到該異常,同時記錄一些日誌。 

spring配置文件相關:

	<!-- 事務管理 -->
	<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接口排在前面。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章