如果要在方法执行前或后或抛出异常后加上一个自己的拦截器,或者一个环绕拦截器,在拦截器中执行一些操作,比如执行一些数据库操作,记录一些信 息,这些操作通过调用一个服务类的方法来执行,这个方法也在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配置文件相关:
 1 <bean id="transactionManager"
 2         class="org.springframework.orm.hibernate3.HibernateTransactionManager">
 3         <property name="sessionFactory" ref="sessionFactory"></property>
 4     </bean>
 5 
 6     <!-- 事务通知 -->
 7     <tx:advice id="txAdvice" transaction-manager="transactionManager">
 8         <tx:attributes>
 9             <tx:method name="get*" read-only="true" />
10             <tx:method name="*" propagation="REQUIRES_NEW"
11                 rollback-for="Exception" />
12         </tx:attributes>
13     </tx:advice>
14 
15     <!-- aop代理设置 -->
16     <aop:config proxy-target-class="true">
17         <aop:pointcut id="txPointcut" expression="execution(* com.hbs..*Service.*(..))" />
18         <aop:pointcut id="logPointcut"
19             expression="execution(* com.hbs.customer..*Service.*(..))" />
20         <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"
21             order="1" />
22         <aop:aspect id="logAspect" ref="logInterceptor" order="2">
23             <aop:after-throwing pointcut-ref="logPointcut"
24                 method="serviceIntercept" />
25         </aop:aspect>
26     </aop:config>
27 
28     <!-- log拦截器类 -->
29     <bean id="logInterceptor" class="com.hbs.eventlog.EventLogInterceptor">
30         <property name="service" ref="logService"></property>
31     </bean>
32 

 拦截器类:
 1 public class EventLogInterceptor implements Ordered {  
 2   
 3     private int order = 1;  
 4       
 5     private EventLogService service;  
 6       
 7     public Object serviceIntercept(ProceedingJoinPoint point) throws Throwable{  
 8         if(point instanceof MethodInvocationProceedingJoinPoint){  
 9             MethodInvocationProceedingJoinPoint mpoint = (MethodInvocationProceedingJoinPoint)point;  
10             //  
11         }  
12         try {   
13             System.out.println("记录日志开始");  
14             service.eventLog();  
15             System.out.println("记录日志结束");  
16         }catch(Exception ex) {  
17             ex.printStackTrace();  
18         }  
19         return null;  
20     }  
21       
22     public void setOrder(int order){  
23         this.order = order;  
24     }  
25     public int getOrder() {  
26         return order;  
27     }  
28     public EventLogService getService() {  
29         return service;  
30     }  
31     public void setService(EventLogService service) {  
32         this.service = service;  
33     }  
34 }  
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接口排在前面。

posted on 2010-10-06 18:04 岁月神偷 阅读(5658) 评论(0)  编辑  收藏 所属分类: Spring

只有注册用户登录后才能发表评论。


网站导航: