Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do you refactor a @Transactional method to split out non-transactional parts

I have a data access class which runs as part of a stand-alone java application. It is currently working which means that a transaction manager is defined but I want to refactor the class to reduce the scope of the transaction but if I do I get org.hibernate.HibernateException: No Hibernate Session bound to thread, and configuration does not allow creation of non-transactional one here which implies that moving the @Transactional has somehow stopped it from being recognised.

My original version had the refactored methods being private but I found a recommendation to change that to public as in some cases the annotation would not be picked up.

public class DoStuff {
    @Transactional
    public void originalMethod() {
        // do database stuff
        ...

        // do non-database stuff that is time consuming
        ...
    }
}

What I want to do is refactor to the following

public class DoStuff {
    public void originalMethod() {
        doDatabaseStuff()

        doNonDatabaseStuff()
    }

    @Transactional
    public void doDatabaseStuff() {
        ...
    }

    public void doNonDatabaseStuff() {
        ...
    }
}
like image 785
Michael Rutherfurd Avatar asked Aug 23 '12 10:08

Michael Rutherfurd


1 Answers

Edit:

You need to understand how Spring proxying works to understand why your refactoring does not work.

Method calls on the object reference will be calls on the proxy, and as such the proxy will be able to delegate to all of the interceptors (advice) that are relevant to that particular method call. However, once the call has finally reached the target object, any method calls that it may make on itself, are going to be invoked against the this reference, and not the proxy. This has important implications. It means that self-invocation is not going to result in the advice associated with a method invocation getting a chance to execute.

@Transactional uses Spring AOP, Spring uses proxies. This means that when you call an @Transactional method from another class, Spring will use a proxy, so the transactional advice will be applied. However, if you call the method from the same class, spring will use the "this" reference instead of the proxy, so that transactional advice will not be applied.

Original Answer:

Here is what worked for me in similar scenario.

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() {           
        ...       
    }   
} 

Explanation:

  1. Make the class ApplicationContextAware, so it has a reference to the context
  2. When you need to call a transactional method, fetch the actual spring proxy from the context
  3. Use this proxy to call your method, so that @Transactional is actually applied.
like image 153
gresdiplitude Avatar answered Oct 09 '22 11:10

gresdiplitude