Skip to main content

JPA service generalization, templeting

JPA made small revolution among ORM tools and EJB 2 world. Easy to use, simple unified way to develop you model and services. After i used it in few projects, i have found that all JPA services are very similar, so this is a simple try to generalize or template them.

This can be for example simple abstract interface for all:
package test.service;

import java.util.List;

public abstract interface JpaService<T> {
 void save(T t);
 T find(Long id);
 void remove(Long id);
 List<T> findAll();
 long count();
}

And abstract implementation class look like this:
package test.service.impl;

import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;

import org.apache.log4j.Logger;

import test.service.JpaService;


public abstract class JpaServiceImpl<T> implements JpaService<T>{
 /** Logger. */
   private static final Logger log = Logger.getLogger(JpaServiceImpl.class);

   /** Entity manager. */
   @PersistenceContext
   protected EntityManager em;
   
   /** Reflection util */
   protected ReflectionUtil reflectionUtil = ReflectionUtil.get();
   
   /**
    * Save entity.
    */
   //@Transactional(readOnly = false, propagation=Propagation.REQUIRED, isolation=Isolation.DEFAULT)
   public void save(T t) {
     log.debug("JpaServiceImpl-Save");
     if (reflectionUtil.getIdValue(t) == null) {
       // new
       log.debug("New");
       em.persist(t);
     } else {
       // update
       log.debug("Update");
       em.merge(t);
     }
     
   }

   /**
    * Remove entity.
    */
   //@Transactional(readOnly = false, propagation=Propagation.REQUIRED)
   public void remove(Long id) {
     log.debug("remove: " + id);
     T t = (T) find(id);
     if (t != null) {
       log.debug("found try to remove");
       em.remove(t);
       log.debug("removed");
     }
   }

   /**
    * Find entity.
    * 
    */
   @SuppressWarnings( { "unchecked" })
   //@Transactional(readOnly = false, propagation=Propagation.REQUIRED)
   public T find(Long id) {
     log.debug("find!!" + id); 
         
     return (T) em.find(reflectionUtil.getModelClass(this), id);
   }

   /**
    * Find all entities.
    */
   @SuppressWarnings("unchecked")
   //@Transactional(readOnly = true, propagation=Propagation.REQUIRED)
   public List<T> findAll() {
     log.debug("JpaServiceImpl-findAll");
     String queryStr = new StringBuilder().append("select t FROM ").append(
      reflectionUtil.getModelClass(this).getName()).append(" t").toString(); //getSimpleName()
     log.debug(queryStr);
     Query query = em.createQuery(queryStr);
     
     return query.getResultList();
   }
   
   public long count() {
     String queryStr = new StringBuilder().append("SELECT COUNT(t) FROM ").append(reflectionUtil.getModelClass(this).getName()).append(" t").toString(); 
     Query query = em.createQuery(queryStr);
     return (Long) query.getSingleResult();
   }

}

Note that even JpaServiceImpl doesnt have any abstract method it is still set to be abstract class. I did this just to enforce that for each entity there is its own implementation of service, since we always going to need few extra special methods for them.

ReflactionUtil its a small class to resolve model beeing used in service and to get id value for it, and looks like this
package test.service.impl;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.util.HashMap;
import java.util.Map;

import javax.persistence.Id;

import org.apache.log4j.Logger;


public class ReflectionUtil {
 /** Logger */
 private static final Logger log = Logger.getLogger(ReflectionUtil.class);

 /** Cache Map of scanned obj */
 private Map<String, Object> map = new HashMap<String, Object>();
 
 /** single instance */
 private static ReflectionUtil instance;
 private ReflectionUtil() {}
 
 public static ReflectionUtil get() {
  if (instance == null) {
   instance = new ReflectionUtil();
  }
  return instance;
 }
 
 public Class getModelClass(Class cls) {
  String key = "modelClass-" + cls.getName();
  if (!map.containsKey(key)) { 
   Class<?> modelClass = (Class<?>) (((ParameterizedType) (cls
    .getGenericSuperclass())).getActualTypeArguments()[0]);
   map.put(key, modelClass);
  }
  return (Class<?>) map.get(key);
 }
 
 public Class<?> getModelClass(Object obj) {
  return getModelClass(obj.getClass());
 }
 
 public Object getIdValue(Object t) {
  try {
   Method method = getIdMethod(t);
   return (Object) method.invoke(t, null);
  } catch (Exception e) {
   log.error(e);
   return null;
  }
 }
 
 private Method getIdMethod(Object t) throws SecurityException, NoSuchMethodException {
  String key = t.getClass().getName();
  //key + "-method"
  if (map.get(key + "-method") == null) {
   String methodNameForId = getMethodNameForId(t);
   log.debug("methodName: " + methodNameForId);
   Method m = t.getClass().getMethod(methodNameForId, null);
   map.put(key + "-method", m);
  }
  return (Method) map.get(key + "-method");
 }
 
 public String getPropertyNameForId(Object t){
  return getMethodOrPropertyNameForId(t, "-propertyNameForId");
 }
 public String getMethodNameForId(Object t){
  return getMethodOrPropertyNameForId(t, "-methodNameForId");
 }
 
 private String getMethodOrPropertyNameForId(Object t, String subKey){
  String key = t.getClass().getName();
  
  if (map.get(key) == null || map.get(key + subKey) == null ) {
   Class<?> modelClass = getModelClass(t);
   // set methodNameForId
   // finds getter name for field which has id annotation
   for (Field f : modelClass.getDeclaredFields()) {
    if (f.isAnnotationPresent(Id.class)) {
     String propertyNameForId = f.getName();
     map.put(key + "-propertyNameForId", propertyNameForId);
     String methodNameForId = "get" + capitalize(f.getName());
     map.put(key + "-methodNameForId", methodNameForId);
     break;
    }
   }
  }
  
  return (String) map.get(key + subKey);
 }
 
 /** Capitalize string */
 private String capitalize(String str) {
  return str.substring(0, 1).toUpperCase()
    + str.substring(1, str.length());
 }
}

After that you extend interface like this
package test.service;

import java.util.List;

import test.model.Person;

public interface PersonService extends JpaService&lt;Person&gt; {
 List&lt;Person&gt; findByLastName(String name);
}

And we can extend implementation service like this
package test.service.impl;

import java.util.List;

import javax.persistence.Query;

import test.model.Person;
import test.service.PersonService;


public class PersonServiceImpl extends JpaServiceImpl&lt;Person&gt; 
  implements PersonService {

 public List&lt;Person&gt; findByLastName(String name) {
  Query query = em.createQuery("SELECT p FROM " + reflectionUtil.getModelClass(this).getName() +
          " p WHERE p.lastName = '" + name + "' ");
     return query.getResultList();
 }

}

Tell me do you use something similar or?

Comments

Darshan said…
Thank you for the information, i found the information very useful.
If anyone looking for Java training in Bangalore i suggest Apponix Technologies, they provide best Java training. For more information visit : https://www.apponix.com/Java-Institute/Java-Training-Institute-in-Bangalore.html