Spring AOP 日志拦截器的事务管理

若是要在方法执行前或后或抛出异常后加上一个本身的拦截器,或者一个环绕拦截器,在拦截器中执行一些操做,好比执行一些数据库操做,记录一些信 息,这些操做经过调用一个服务类的方法来执行,这个方法也在spring事务管理拦截器的管理之下,那么这个记录方法须要在另外一个事务中进行,而不是与被 拦截方法在同一个事务中,否则若是被拦截方法抛出异常须要回滚时,所做的记录也会被回滚,固然有时候确实须要同时回滚,那就要放在同一个事务中。 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方法上同时加上一个异常拦截器,当用户不存在或密码不正确时用户检测方法会抛出异 常,异常拦截器捕获到该异常,同时记录一些日志。 express

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

 

 

Spring中事务与aop的前后顺序问题ide

 Spring中的事务是经过aop来实现的,当咱们本身写aop拦截的时候,会遇到跟spring的事务aop执行的前后顺序问题,好比说动态切换数据源的问题,若是事务在前,数据源切换在后,会致使数据源切换失效,因此就用到了Order(排序)这个关键字.测试

        咱们能够经过在@AspectJ的方法中实现org.springframework.core.Ordered 这个接口来定义order的顺序,order 的值越小,说明越先被执行。好比代码以下:ui

/**

 * @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 字段,代码以下:hibernate

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

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

这样就实现了咱们本身写的aop在事务介入以前就执行了!代理

相关文章
相关标签/搜索