spring声明式事务 同一类内方法调用事务失效

原文地址:https://blog.csdn.net/jiesa/article/details/53438342 

一 宏观说明

[问题]

       Spring的声明式事务,我想就不用多介绍了吧,一句话“自从用了Spring AOP啊,事务管理真轻松啊,真轻松;事务管理代码没有了,脑不酸了,手不痛了,一口气全配上了事务;轻量级,测试起来也简单,嘿!”。不管从哪个角度看,轻量级声明式事务都是一件解放生产力的大好事。所以,我们“一直用它”。

      不过,最近的一个项目里,却碰到了一个事务管理上的问题:有一个服务类,其一个声明了事务的方法,里面做了三次插入SQL操作,但是在后面出错回滚时,却发现前面插入成功了,也是说,这个声明了事务的方法,实际上并没有真正启动事务!怎么回事呢?难道Spring的声明式事务失效了?

[探幽]

     其实以前也会碰到有人说,Spring的事务配置不起作用,但是根据第一反应和以往经验,我总会告诉他,肯定是你的配置有问题啦;所以这一次,我想也不会例外,大概是把事务注解配在了接口上而不是实现方法上,或者,如果是用XML声明方式的话,很可能是切入点的表达式没有配对。

     不过,在检查了他们的配置后,却发现没有配置问题,该起事务的实现方法上,用了@Transactional事务注解声明,XML里也配了注解驱动,配置很正确啊,怎么会不起作用?

     我很纳闷,于是往下问:

     问1:其他方法有这种情况么?

     答1:没有。

     问2:这个方法有什么特别的么(以下简称方法B)?

     答2:就是调后台插了三条记录啊,没啥特别的。

     问3:这个方法是从Web层直接调用的吧?

     答3:不是,是这个Service类(以下简称ServiceA)的另外一个方法调过来的(以下简称方法A)。

     问4:哦,那个调用它的方法配了事务么(问题可能在这了)?

     答4:没有。

     问5:那WEB层的Action(用的是Struts2),调用的是没有声明事务的方法A,方法A再调用声明了事务的方法B?

     答5:对的。

     问6:你直接在方法A上加上事务声明看看

     答6:好。。。

     看来可能找到问题所在了,于是把@Transactional也加在方法A上,启动项目测试,结果是:事务正常生效,方法A和方法B都在一个事务里了。

 

     好了,现在总结一下现象:

     1、ServiceA类为Web层的Action服务

     2、Action调用了ServiceA的方法A,而方法A没有声明事务(原因是方法A本身比较耗时而又不需要事务)

     3、ServiceA的方法A调用了自己的方法B,而方法B声明了事务,但是方法B的事务声明在这种情况失效了。

     4、如果在方法A上也声明事务,则在Action调用方法A时,事务生效,而方法B则自动参与了这个事务。 

       我让他先把A也加上事务声明,决定回来自己再测一下。

       这个问题,表面上是事务声明失效的问题,实质上很可能是Spring的AOP机制实现角度的问题。我想到很久以前研究Spring的AOP实现时发现的一个现象:对于以Cglib方式增强的AOP目标类,会创建两个对象,一个事Bean实例本身,一个是Cglib增强代理对象,而不仅仅是只有后者。我曾经疑惑过这一点,但当时没有再仔细探究下去。

      我们知道,Spring的AOP实现方式有两种:1、Java代理方式;2、Cglib动态增强方式,这两种方式在Spring中是可以无缝自由切换的。Java代理方式的优点是不依赖第三方jar包,缺点是不能代理类,只能代理接口。

 

      Spring通过AopProxy接口,抽象了这两种实现,实现了一致的AOP方式:

现在看来,这种抽象同样带了一个缺陷,那就是抹杀了Cglib能够直接创建普通类的增强子类的能力,Spring相当于把Cglib动态生成的子类,当普通的代理类了,这也是为什么会创建两个对象的原因。下图显示了Spring的AOP代理类的实际调用过程:


因此,从上面的分析可以看出,methodB没有被AopProxy通知到,导致最终结果是:被Spring的AOP增强的类,在同一个类的内部方法调用时,其被调用方法上的增强通知将不起作用。

    

      而这种结果,会造成什么影响呢:

      1:内部调用时,被调用方法的事务声明将不起作用

      2:换句话说,你在某个方法上声明它需要事务的时候,如果这个类还有其他开发者,你将不能保证这个方法真的会在事务环境中

      3:再换句话说,Spring的事务传播策略在内部方法调用时将不起作用。不管你希望某个方法需要单独事务,是RequiresNew,还是要嵌套事务,要Nested,等等,统统不起作用。

      4:不仅仅是事务通知,所有你自己利用Spring实现的AOP通知,都会受到同样限制。。。。

 

[解难]

      问题的原因已经找到,其实,我理想中的AOP实现,应该是下面这样:

只要一个Cglib增强对象就好,对于Java代理方式,我的选择是毫不犹豫的抛弃。

      至于前面的事务问题,只要避开Spring目前的AOP实现上的限制,要么都声明要事务,要么分开成两个类,要么直接在方法里使用编程式事务,那么一切OK。

二 具体解释

      前些日子一朋友在需要在目标对象中进行自我调用,且需要实施相应的事务定义,且网上的一种通过BeanPostProcessor的解决方案是存在问题的。因此专门写此篇帖子分析why。

1、预备知识
aop概念请参考【http://www.iteye.com/topic/1122401】和【http://jinnianshilongnian.iteye.com/blog/1418596】

spring的事务管理,请参考【http://jinnianshilongnian.iteye.com/blog/1441271】

 

使用AOP 代理后的方法调用执行流程,如图所示


也就是说我们首先调用的是AOP代理对象而不是目标对象,首先执行事务切面,事务切面内部通过TransactionInterceptor环绕增强进行事务的增强,即进入目标方法之前开启事务,退出目标方法时提交/回滚事务。

2、测试代码准备

Java代码  
public interface AService {  
    public void a();  
    public void b();  
}  
   
@Service()  
public class AServiceImpl1 implements AService{  
    @Transactional(propagation = Propagation.REQUIRED)  
    public void a() {  
        this.b();  
    }  
    @Transactional(propagation = Propagation.REQUIRES_NEW)  
    public void b() {  
    }  
}  

 

3、问题
目标对象内部的自我调用将无法实施切面中的增强,如图所示


 

此处的this指向目标对象,因此调用this.b()将不会执行b事务切面,即不会执行事务增强,因此b方法的事务定义“@Transactional(propagation = Propagation.REQUIRES_NEW)”将不会实施,即结果是b和a方法的事务定义是一样的,可以从以下日志看出:

 

 org.springframework.transaction.annotation.AnnotationTransactionAttributeSource Adding transactional method 'a' with attribute: PROPAGATION_REQUIRED,ISOLATION_DEFAULT; ''

org.springframework.beans.factory.support.DefaultListableBeanFactory Returning cached instance of singleton bean 'txManager'

org.springframework.orm.hibernate4.HibernateTransactionManager Creating new transaction with name [com.sishuok.service.impl.AServiceImpl1.a]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT; ''  -----创建a方法事务

org.springframework.orm.hibernate4.HibernateTransactionManager Opened new Session …… for Hibernate transaction  ---打开Session

……

org.springframework.transaction.support.TransactionSynchronizationManager Initializing transaction synchronization

org.springframework.transaction.interceptor.TransactionInterceptor Getting transaction for [com.sishuok.service.impl.AServiceImpl1.a]

org.springframework.transaction.interceptor.TransactionInterceptor Completing transaction for [com.sishuok.service.impl.AServiceImpl1.a] ----完成a方法事务

org.springframework.orm.hibernate4.HibernateTransactionManager Triggering beforeCommit synchronization

org.springframework.orm.hibernate4.HibernateTransactionManager Triggering beforeCompletion synchronization

org.springframework.orm.hibernate4.HibernateTransactionManager Initiating transaction commit

 

org.springframework.orm.hibernate4.HibernateTransactionManager Committing Hibernate transaction on Session ……---提交a方法事务

org.springframework.orm.hibernate4.HibernateTransactionManager Rolling back Hibernate transaction on Session ……---如果有异常将回滚a方法事务

 

org.springframework.orm.hibernate4.HibernateTransactionManager Triggering afterCommit synchronization

org.springframework.orm.hibernate4.HibernateTransactionManager Triggering afterCompletion synchronization

org.springframework.transaction.support.TransactionSynchronizationManager Clearing transaction synchronization

……

org.springframework.orm.hibernate4.HibernateTransactionManager Closing Hibernate Session …… after transaction     --关闭Session

 

我们可以看到事务切面只对a方法进行了事务增强,没有对b方法进行增强。

 

三 解决方案

    此处a方法中调用b方法时,只要通过AOP代理调用b方法即可走事务切面,即可以进行事务增强,如下所示:

Java代码  
public void a() {  
    aopProxy.b();//即调用AOP代理对象的b方法即可执行事务切面进行事务增强  
}  
 
判断一个Bean是否是AOP代理对象可以使用如下三种方法:

AopUtils.isAopProxy(bean)        : 是否是代理对象;

AopUtils.isCglibProxy(bean)       : 是否是CGLIB方式的代理对象;

AopUtils.isJdkDynamicProxy(bean) : 是否是JDK动态代理方式的代理对象;

3.1、通过ThreadLocal暴露Aop代理对象
1、开启暴露Aop代理到ThreadLocal支持(如下配置方式从spring3开始支持)

Java代码  
  
Java代码  
   
   

2、修改我们的业务实现类

this.b();-----------修改为--------->((AService) AopContext.currentProxy()).b();

 

3、执行测试用例,日志如下

 

 

org.springframework.beans.factory.support.DefaultListableBeanFactory Returning cached instance of singleton bean 'txManager'

org.springframework.orm.hibernate4.HibernateTransactionManager Creating new transaction with name [com.sishuok.service.impl.AServiceImpl2.a]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT; ''   -----创建a方法事务

org.springframework.orm.hibernate4.HibernateTransactionManager Opened new Session ……for Hibernate transaction  --打开a Session

org.springframework.orm.hibernate4.HibernateTransactionManager Preparing JDBC Connection of Hibernate Session ……

org.springframework.orm.hibernate4.HibernateTransactionManager Exposing Hibernate transaction as JDBC transaction ……

……

org.springframework.transaction.support.TransactionSynchronizationManager Initializing transaction synchronization

org.springframework.transaction.interceptor.TransactionInterceptor Getting transaction for [com.sishuok.service.impl.AServiceImpl2.a]

 

org.springframework.transaction.annotation.AnnotationTransactionAttributeSource Adding transactional method 'b' with attribute: PROPAGATION_REQUIRES_NEW,ISOLATION_DEFAULT; ''

……

org.springframework.orm.hibernate4.HibernateTransactionManager Suspending current transaction, creating new transaction with name [com.sishuok.service.impl.AServiceImpl2.b]  -----创建b方法事务(并暂停a方法事务)

……

org.springframework.orm.hibernate4.HibernateTransactionManager Opened new Session  for Hibernate transaction  ---打开b Session

……

org.springframework.transaction.support.TransactionSynchronizationManager Initializing transaction synchronization

org.springframework.transaction.interceptor.TransactionInterceptor Getting transaction for [com.sishuok.service.impl.AServiceImpl2.b]

org.springframework.transaction.interceptor.TransactionInterceptor Completing transaction for [com.sishuok.service.impl.AServiceImpl2.b] ----完成b方法事务

 

org.springframework.orm.hibernate4.HibernateTransactionManager Triggering beforeCommit synchronization

org.springframework.orm.hibernate4.HibernateTransactionManager Triggering beforeCompletion synchronization

org.springframework.orm.hibernate4.HibernateTransactionManager Initiating transaction commit

org.springframework.orm.hibernate4.HibernateTransactionManager Committing Hibernate transaction on Session …… ---提交b方法事务

org.springframework.orm.hibernate4.HibernateTransactionManager Triggering afterCommit synchronization

org.springframework.orm.hibernate4.HibernateTransactionManager Triggering afterCompletion synchronization

org.springframework.transaction.support.TransactionSynchronizationManager Clearing transaction synchronization

……

org.springframework.orm.hibernate4.HibernateTransactionManager Closing Hibernate Session …… after transaction  --关闭 b Session

 

-----到此b方法事务完毕

 

org.springframework.orm.hibernate4.HibernateTransactionManager Resuming suspended transaction after completion of inner transaction ---恢复a方法事务

……

org.springframework.transaction.support.TransactionSynchronizationManager Initializing transaction synchronization

org.springframework.transaction.interceptor.TransactionInterceptor Completing transaction for [com.sishuok.service.impl.AServiceImpl2.a] ----完成a方法事务

org.springframework.orm.hibernate4.HibernateTransactionManager Triggering beforeCommit synchronization

org.springframework.orm.hibernate4.HibernateTransactionManager Triggering beforeCompletion synchronization

org.springframework.orm.hibernate4.HibernateTransactionManager Initiating transaction commit

org.springframework.orm.hibernate4.HibernateTransactionManager Committing Hibernate transaction on Session ……---提交a方法事务

org.springframework.orm.hibernate4.HibernateTransactionManager Triggering afterCommit synchronization

org.springframework.orm.hibernate4.HibernateTransactionManager Triggering afterCompletion synchronization

org.springframework.transaction.support.TransactionSynchronizationManager Clearing transaction synchronization

……

org.springframework.orm.hibernate4.HibernateTransactionManager Closing Hibernate Session …… after transaction  --关闭 a Session

此处我们可以看到b方法的事务起作用了。

以上方式是解决目标对象内部方法自我调用并实施事务的最简单的解决方案。

 

4、实现原理分析

  • 4.1、在进入代理对象之后通过AopContext.serCurrentProxy(proxy)暴露当前代理对象到ThreadLocal,并保存上次ThreadLocal绑定的代理对象为oldProxy;
  • 4.2、接下来我们可以通过 AopContext.currentProxy() 获取当前代理对象;
  • 4.3、在退出代理对象之前要重新将ThreadLocal绑定的代理对象设置为上一次的代理对象,即AopContext.serCurrentProxy(oldProxy)。

 

有些人不喜欢这种方式,说通过ThreadLocal暴露有性能问题,其实这个不需要考虑,因为事务相关的(Session和Connection)内部也是通过SessionHolder和ConnectionHolder暴露到ThreadLocal实现的。

 

不过自我调用这种场景确实只有很少情况遇到,因此不用这种方式我们也可以通过如下方式实现。

3.2、通过初始化方法在目标对象中注入代理对象
Java代码  

@Service  
public class AServiceImpl3 implements AService{  
    @Autowired  //①  注入上下文  
    private ApplicationContext context;  
      
    private AService proxySelf; //②  表示代理对象,不是目标对象  
    @PostConstruct  //③ 初始化方法  
    private void setSelf() {  
        //从上下文获取代理对象(如果通过proxtSelf=this是不对的,this是目标对象)  
        //此种方法不适合于prototype Bean,因为每次getBean返回一个新的Bean  
        proxySelf = context.getBean(AService.class);   
    }  
    @Transactional(propagation = Propagation.REQUIRED)  
    public void a() {  
       proxySelf.b(); //④ 调用代理对象的方法 这样可以执行事务切面  
    }  
    @Transactional(propagation = Propagation.REQUIRES_NEW)  
    public void b() {  
    }  
}  


 
此处日志就不分析,和3.1类似。此种方式不是很灵活,所有需要自我调用的实现类必须重复实现代码。

你可能感兴趣的:(javaweb,spring)