若是要在方法执行前或后或抛出异常后加上一个本身的拦截器,或者一个环绕拦截器,在拦截器中执行一些操做,好比执行一些数据库操做,记录一些信 息,这些操做经过调用一个服务类的方法来执行,这个方法也在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在事务介入以前就执行了!代理