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

时间:2012-08-23 10:47:01

标签: java hibernate transactions hibernate-annotations spring-annotations

我有一个数据访问类,它作为独立java应用程序的一部分运行。它目前正在工作,这意味着定义了一个事务管理器,但我想重构该类以减少事务的范围,但如果我这样做,我得到 org.hibernate.HibernateException:没有Hibernate Session绑定到线程,并且配置这里不允许创建非事务性的,这意味着移动 @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() {
        ...
    }
}

2 个答案:

答案 0 :(得分:7)

修改

您需要了解how Spring proxying works才能理解为什么您的重构不起作用。

  

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

@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. 当您需要调用事务方法时,从上下文
  3. 中获取实际的spring代理
  4. 使用此代理调用您的方法,以便实际应用@Transactional。

答案 1 :(得分:0)

你的方法看起来应该可以正常工作,我希望这个问题与Spring代理有关。

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

如果您班级的实际定义是:

public class DoStuff implements Doable {
  public void originalMethod() {

  }
}

public interface Doable {
  public void originalMethod();
}

如果这确实是结构,当你转移到新结构时,Spring无法代理新的doDatabaseStuff方法。

您可以选择解决此问题:

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