如何重构@Transactional方法来拆分非事务部分



我有一个数据访问类,它作为独立java应用程序的一部分运行。它目前正在工作,这意味着定义了一个事务管理器,但我想重构类以减少事务的范围,但如果我这样做,我会得到org.hibernate.HHibernate异常:没有绑定到线程的hibernate会话,并且配置不允许在此处创建非事务性,这意味着移动@transactional在某种程度上阻止了它被识别。

我的原始版本中重构的方法是私有的,但我发现建议将其更改为公共的,因为在某些情况下,注释不会被拾取。

public class DoStuff {
    @Transactional
    public void originalMethod() {
        // do database stuff
        ...
        // do non-database stuff that is time consuming
        ...
    }
}

我想做的是重构到以下

public class DoStuff {
    public void originalMethod() {
        doDatabaseStuff()
        doNonDatabaseStuff()
    }
    @Transactional
    public void doDatabaseStuff() {
        ...
    }
    public void doNonDatabaseStuff() {
        ...
    }
}

编辑:

您需要了解Spring代理是如何工作的,才能理解为什么重构不起作用。

对对象引用的方法调用将是对代理的调用,因此代理将能够委托给与该特定方法调用相关的所有拦截器(建议)。然而,一旦调用最终到达目标对象,它可能对自己进行的任何方法调用都将针对该引用而不是代理进行调用。这具有重要意义。这意味着自调用不会导致与方法调用相关的建议有机会执行。

@Transactional使用Spring AOP,Spring使用代理。这意味着,当您从另一个类调用@Transactional方法时,Spring将使用代理,因此将应用事务性建议。但是,如果从同一个类调用该方法,spring将使用"this"引用而不是代理,这样就不会应用事务性建议。

原始答案:

以下是在类似情况下对我有效的方法。

public class DoStuff implement ApplicationContextAware {    
private ApplicationContext CONTEXT;
public void setApplicationContext(ApplicationContext context) throws BeansException {
    CONTEXT = context;
}
    public void originalMethod() {           
        getSpringProxy().doDatabaseStuff()              
        doNonDatabaseStuff()       
    }
    private DoStuff getSpringProxy() {
        return context.getBean(this.getClass());     
    } 
    @Transactional       
    public void doDatabaseStuff() {           
        ...       
    }          
    public void doNonDatabaseStuff() {           
        ...       
    }   
} 

说明:

  1. 将类设为ApplicationContextAware,以便它具有对上下文的引用
  2. 当您需要调用事务性方法时,从上下文中获取实际的spring代理
  3. 使用此代理调用您的方法,以便实际应用@Transactional

您的方法看起来应该可以正常工作,我认为问题与Spring代理有关。

我询问接口的原因与Spring应用事务行为的默认方法JDK动态代理有关。

如果你的类的实际定义是:

public class DoStuff implements Doable {
  public void originalMethod() {
  }
}
public interface Doable {
  public void originalMethod();
}

如果这确实是结构,那么当您移动到新结构时,Spring无法代理新的doDatabaseStuff方法。

您可以选择修复此问题:

  • 将新方法添加到接口中,以确保Spring可以代理它们
  • 转到使用基于CGLIB的代理(这些代理不依赖于接口)

最新更新