06 April 2010

Using Dynamic Proxies to centralize JPA code

I was working on some project that required me not to use an Application Server, hence I've chosen JPA to persist data, I had to use JPA in standalone application (actually it is a web application that intended to run in tomcat)

One solution is to use some lightweight container such as Spring instead of real Containers, but this project was so simple that i needn't to complex the Dev process (and introduce many jars ....)

I went with using Java Daynaic Proxy to centralize the code/control of the JPA; here's the code of the proxy:

package com.forat.service;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;

import com.forat.service.exceptions.DAOException;

/**
 * Example of usage :
 * <pre>
 * OnlineFromService onfromService = 
 *            (OnlineFromService) DAOProxy.newInstance(new OnlineFormServiceImpl());
 *        try {
 *            Student s = new Student();
 *            s.setName("Mohammed");
 *            s.setNationalNumber("123456");
 *            onfromService.addStudent(s);    
 *        }catch (Exception ex) {
 *            System.out.println(ex.getMessage());
 *        }
 *</pre>
 * @author mohammed hewedy
 *
 */
public class DAOProxy implements InvocationHandler{

    private Object object;
    private Logger logger = Logger.getLogger(this.getClass().getSimpleName());

    private DAOProxy(Object object) {
        this.object = object;
    }
    
    public static Object newInstance(Object object) {
        return Proxy.newProxyInstance(object.getClass().getClassLoader(), 
                    object.getClass().getInterfaces(), new DAOProxy(object));
    }
    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        EntityManagerFactory emf = null;
        EntityManager em = null;
        EntityTransaction et = null;
        Object result = null;
        try {
            emf = Persistence.createEntityManagerFactory(Constants.UNIT_NAME);
            em = emf.createEntityManager();;
            Method entityManagerSetter = object.getClass().
                getDeclaredMethod(Constants.ENTITY_MANAGER_SETTER_METHOD, EntityManager.class);
            entityManagerSetter.invoke(object, em);
            et = em.getTransaction();
            et.begin();
            result = method.invoke(object, args);
            et.commit();
            return result;
        }catch (InvocationTargetException ex) {  // Exception in Called code
            // Any exception that occur in the invoked method body can be got by ex.getCause();
// you can rollback transaction here 
        }catch (Exception ex) {// Exception in Proxy code
            et.rollback();
            Throwable cause = ex.getCause();
            logger.log(Level.SEVERE, cause.getMessage());
            if (cause instanceof DAOException)
                throw new DAOException(cause.getMessage(), cause);
            else
                throw new RuntimeException(cause.getMessage(), cause);
        }finally {
            em.close();
            emf.close();
        }
    }
}

Here's the service Interface "OnlineFromService":
package com.forat.service;

import java.util.Set;

import com.forat.model.Student;
import com.forat.service.exceptions.StudentNotFoundException;

public interface OnlineFromService {
    
    public void fillFaculties(String[] facultiesNames);
    public void addFaculty(String facultyName);
    
    public void fillGovernrates(String[] GovernratesNames);
    public void addGovernrate(String GovernrateName);
    
    public void addStudent(Student student);
    public Student findStudent(String nationalNumber) throws StudentNotFoundException;
    public void updateStudent(Student student);
    
    public Set<Student> getAllStudents();
}

And here's the Service Implementation:
import java.util.Set;
import java.util.logging.Logger;

import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.NonUniqueResultException;
import javax.persistence.Query;

import com.forat.model.Student;
import com.forat.service.exceptions.StudentNotFoundException;

/**
 * This class should be called through {@link DAOProxy} as it injects the {@link EntityManager} and other JPA staff
 * and takes care of the transactions (commiting/rollbacking) , etc...
 * @see DAOProxy
 * @author mohammed
 */
public class OnlineFormServiceImpl implements OnlineFromService {

    private Logger logger = Logger.getLogger(this.getClass().getSimpleName());
    
    private EntityManager em;
    
    public OnlineFormServiceImpl() {
    }
    
    public void setEntityManager(EntityManager em) {
        this.em = em;
    }
    // the code has been minimized, the code that is responsalbe of working with JPA staff has moved to the proxy class
    // we we do here just use the EntityManager (as if we were in a managed environment)
    public void addStudent(Student student) {
        em.persist(student);
    }

    // other interface methods goes here ...

}

The idea is to centralize the control of the persistence code, and this implementation is a basic implementation that may contains errors.
Also, this code my includes performance impact, as I used reflections to invoke service methods, but for sure its performance impact will be more less than of application servers!

1 comment: