這和自己的攔截器和事務管理的攔截器的執行順序有一定關係,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接口排在前面。