Thursday, March 10, 2011

Spring managed service classes post-initialization using transaction template

In my recent work , where we do use Hibernate and Spring for services I encountered a common problem with initialization of the services, which do need to have already transaction management initialized before they are executed. If you got a managed Spring beans and you would like to do some post processing of the managed bean you might use standard @PostConstruct annotation, which is processed after the BeanFactory creates the bean. Unfortunately there is no guarantee that in the time of calling @PostConstruct all other managed beans are properly initialized yet and transaction management is not initialized as well. So problem is if  you need to initialize some services at startup, which do need access to the hibernate session and spring transaction management. You could use @PostInitialize annotation. Unfortunately even though this is a good approach I have encountered a problems even with this post processor in combination with annotation @Transactional. Simply if you have called a method annotated with both of the annotations then in some cases it still was giving well-known error

"Caused by: org.hibernate.HibernateException: No Hibernate Session bound to thread, and configuration does not allow creation of non-transactional one here"

Consider code snippet below.

Example 1:

This is example service class:

@Service
public class Service{

    @Transactional
    @PostInitialize
    public void initMethod(){
      
         daoService.doSomeWork();
    }
}


Here is DAO class:

@Repository
public class DaoService{

    public void doSomeWork(){
    }
}

I have realized that in some cases @PostInitialize has been working, but I needed to put @Transactional annotation into the DaoService as well. Thus after some time playing around with this custom annotation  I realized this is not a good approach. So finally I have used simple, but reliable approach for initialization of the spring managed services, which do need to get hibernate session.

I have defined a transaction template in spring context.xml (you might specify paremeters of the transaction):

<bean id="sharedTransactionTemplate"

      class="org.springframework.transaction.support.TransactionTemplate">

     <property name="transactionManager" ref="transactionManager"/  >

</bean>

Then I have defined kind of base class for all service which do need to be initialized within transaction:
@Service
public class SpringInitializedService implements ApplicationListener<SpringContextInitialized> {

 
    @Inject
    private TransactionTemplate tt;
    private static final Log log = LogFactory.getLog(SpringInitializedService.class);

    public SpringInitializedService() {
    }
  
    public SpringInitializedService(PlatformTransactionManager tm){
        tt.setTransactionManager(tm);
    }

    @Override
    public void onApplicationEvent(SpringContextInitialized event) {
        tt.execute(new TransactionCallbackWithoutResult() {
            @Override
            protected void doInTransactionWithoutResult(TransactionStatus status) {
                try{
                initialize();
                }
                catch (Exception ex){
                    log.error("Error during initialization",ex);
                    status.setRollbackOnly();
                }
            }
        });

    }
    public void initialize(){
    }

Now managed service classes, which do need transaction (and hibernate session) during initialization  just need to extend this class and override initialize() method with its own code. Last step is to fire SpringContextInitialized event , when spring context is initialized and you are done. Some adjustments of this approach would be needed if you need to affect the order of the initialized services, but this was not my case so I did not care about that.

No comments:

Post a Comment