Friday, January 1, 2010

Hibernate Transaction Management via Dynamic Proxy

In enterprise applications, transaction management strategy is a very important concept developers need to analyze at early stages of any project architecture cycle.
Inspired by reading book, I started thinking in how to strike a balance in working directly with hibernate but yet reducing all that boiler-plate redundant session/transaction(commit,rollback) code, also taking the less intrusive approach via demarcation similar as spring 3 @Transactional as well as reducing external dependencies lock-in. So here's a custom lab reference implementation sample yet simple transaction proxy, let's revisit few patterns in which we'll cover in this post:
Proxy Pattern: proxy is a wrapper functioning as an interface intercepting targeted object. Decorator Pattern: decorator allows additional behaviour to be added to an existing object dynamically. DAO pattern: data layer/access/persistent operations should be isolated in DAO interfaces + implementations.
Let's begin by looking at a partial sample configuration:
...

...











...
We certainly could use @Autowire or @Inject internally for collaborators but for the sake of simplicity and demonstration purposes we'll leave as is for now to set the stage and later on we'll inject a proxied target, note that we'll foster composition over inheritance in the implementations, now let's take a look at a simple annotation(method target) for our transaction demarcations:
package com.martin.orm.tx.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface HbmProxiedTransaction { }
Here's a sample interface:
package com.martin.domain.service.dao;
import java.util.List;
import com.martin.domain.service.dto.Profile;
import com.martin.orm.tx.annotation.HbmProxiedTransaction;
public interface SampleInterface {
// note that is not demarcated, usr programmatic hbm approach
public Profile authenticatePrincipal( final String principal, final String pwd );

@HbmProxiedTransaction
public [T] List[T] execSomeQueryHere( final String someCriteriaBy );
}
Note that demarcation takes place at the interface level, this is due to java proxies nature (if you need to proxy a class that has no interface you would need a library like cglib to do so), it could well also be demarcated at the service interface (preferable).
Now here's the proxy implementation (based on org.hibernate.context.ThreadLocalSessionContext configuration)
package com.martin.orm.tx;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import org.hibernate.SessionFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.martin.orm.tx.annotation.HbmProxiedTransaction;

public class HbmTransactionProxyMgr implements InvocationHandler {

private final Logger log = LoggerFactory.getLogger(HbmTransactionProxyMgr.class);

private final Object target;
private final SessionFactory sf;

private HbmTransactionProxyMgr( final SessionFactory sf , final Object target ) {
this.sf  = sf;
this.target = target;
}

public static [T] T
newInstance(final SessionFactory sf, final Object target, final Class[T] expectedType ) {
return expectedType.cast(
  Proxy.newProxyInstance(
     target.getClass().getClassLoader() ,
     target.getClass().getInterfaces()  ,
     new HbmTransactionProxyMgr( sf , target )
   )
 );
}

@Override
public Object
invoke( final Object proxy , final Method method , final Object[] args ) throws Throwable {
if ( proxy == null || method == null ) return null;
Object ret = null;
try {

if ( !method.isAnnotationPresent( HbmProxiedTransaction.class )) {
 return method.invoke( target , args );
}

 sf.getCurrentSession().beginTransaction();
 /////////////////////////////////////
 ret = method.invoke( target , args );
 /////////////////////////////////////
 sf.getCurrentSession().getTransaction().commit();

 log.debug("-=[ tx.proxy ]=-commited()");

} catch (final InvocationTargetException ex) {
 if(sf.getCurrentSession().getTransaction().isActive()) {
    sf.getCurrentSession().getTransaction().rollback();
    log.warn("-=[ tx.proxy ]=-({})rolledback() -> {}" ,
    method.getName(),((ex.getCause()!=null)?ex.getCause().getMessage():ex) );
 }
 throw ex.getTargetException();
} finally {
 if ( ret != null && log.isDebugEnabled() )
 log.debug("-=[ tx.proxy ]=-ret: {}",ret.toString());
}
return ret;
}
}
And here's a new annotation to instruct a tx bean processor for the proxied injection:
package com.martin.orm.tx.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface InjectProxiedHbmTxMgr { }
And the spring bean post processor to inject @InjectProxiedHbmTxMgr
package com.martin.orm.tx;

import java.lang.reflect.Field;
import org.hibernate.SessionFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.FatalBeanException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.ApplicationContext;
import org.springframework.util.ReflectionUtils;
import com.martin.orm.tx.annotation.InjectProxiedHbmTxMgr;

public class HbmTransactionProxyProcessor implements BeanPostProcessor {

private final Logger log = LoggerFactory.getLogger(HbmTransactionProxyProcessor.class);

@Autowired
private ApplicationContext ctx;
private final SessionFactory sf;

public HbmTransactionProxyProcessor( final SessionFactory sf ) {
 this.sf = sf;
}

public Object
postProcessBeforeInitialization(final Object bean, final String beanName) throws BeansException {
 return bean;
}

public Object
postProcessAfterInitialization(final Object bean, final String beanName) throws BeansException {

ReflectionUtils.doWithFields( bean.getClass() , new ReflectionUtils.FieldCallback() {
public void doWith(final Field field ) {
 if (field != null && field.isAnnotationPresent(InjectProxiedHbmTxMgr.class) ) {
   final Class type = field.getType();
   final Object dao = ctx.getBean( type );
   if ( dao != null ) {
    try {
      field.setAccessible(true);
      field.set( bean , HbmTransactionProxyMgr.newInstance( sf , dao , type ) ); //!! set
      log.debug("Injected @InjectProxiedHbmTxMgr {}.{} field" , bean.toString() , field.getName() );
    } catch (final IllegalAccessException e) {
      log.error("Injection @InjectProxiedHbmTxMgr for {}.{} field", bean.toString() , field.getName() );
      throw new FatalBeanException(e.getMessage());
    }
   } else {
      log.error("Injection type({}) not found @InjectProxiedHbmTxMgr", field.getType() );
      throw new FatalBeanException(field.getType()+" not found in appContext");
   }
 }
}
});
return bean;
}
}
And finally a snippet of a service impl:
package com.martin.domain.service;
import java.util.List;
import com.martin.domain.service.dao.SampleDaoInterface;
import com.martin.domain.service.dto.Profile;
import com.martin.orm.tx.HbmTransactionProxyFactoryMgr;

public class ServiceImpl implements ServiceInterface {

// for reference processor will inject a proxied obj
@InjectProxiedHbmTxMgr
private SampleDaoInterface dao;

// for reference this dao impl would internally do usr manual/programmatic tx approach
public Profile authenticatePrincipal( final String principal , final String pwd ) {
 return dao.authenticatePrincipal(principal,pwd);
}

// for reference this method is annotated so dao impl would not have boiler-plate code
public [T] List[T] getSomeEntities( final String someCriteriaBy ) throws SomeException {
 return dao.execSomeQueryHere( someCriteriaBy );
}

}
Note that the dao will contain an assigned proxied dao in which interfaces annotated @HbmProxiedTransaction will be wrapped within transaction (commit/rollback) which eliminates tons of boiler-plate + redundant code, also note that if we instead annotate service interface then multiple daos then could participate within a session transaction context.
Summary: This dynamic proxy concept could be applied to many other needs and taken to so many different levels which as demonstrated is based on the decorator pattern in which for instance the AOP concept is based on as well: foster modularization via separation of concerns with the capability to override / intercept / advice targeted behavior, in real production project I would eventually and preferably go with a well known and standard implementation (spring @Transactional, etc) approach for many reasons outside of this scope (if JTA with global transactions, etc), yet this example is just to depict how a dynamic proxy could be applied in an application with a custom local transaction management implementation. Cheers!

1 comments:

self said...

I like your post.For more information about transaction management software .