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

Popular posts from this blog

Javascript REST client

Note : I still work on text, code example should be fine. REST is the one of the most popular interfaces on the web today. One part to its success it owes to its simplicity. How number of sites that support REST grows we will need some fast and good solution to use those services on client side. Even if its possible to do it all, since REST is based on http, with old AJAX calls, it would be nice to have some little more... This is one approach that i choose. I create in javascript new object that has six methods five for REST methods, four for POST, PUT, GET and REMOVE plus one more to GET all resources, and one more to get html template to display your data. This code is based on jquery and json js libs. function RestServiceJs(newurl) { this.myurl = newurl; this.add = function(model, callback) { $.ajax({ type: 'POST', url: this.myurl, data: JSON.stringify(model), // '{"name":"' + model.name + '"}',

Use JPA with MongoDb and Datanucleus

Web applications nowadays have huge demand for data processing, and they need to get them, process them, and displayed them fast. Traditional relational tables and big SQL engines fail to serve that purpose. NoSQL movement is on the rise. There are huge numbers of alternatives to SQL like BigTable, HBase, CouchDB, Cassandra and MongoDB. They are all fast but you'll need to implement new way to work with them, since all of them have they own query languages. It would be nice that we can use them in similar or same way in our projects, after all ORM layer is there for that to decouple our objects from underlying storage. MongoDB is one of most popular NoSQL solutions and easiest one to install. For HBase i would had to install cygwin or virtual linux, and still will be hard to confgiure it right. MongoDb is easy, it plays out of the box, just unzip it, and it still offers some nice features like MapReduce, Replication and Sharding. Datanucleus is one of the leading ORM provid

Gestalt Diffing algorithm in Java

What we see depends mainly on what we look for. John Lubbock We do not see things as they are, we see things as we are. Anais Nin Gestalt Diffing algorithm in Java Intro When it comes down to diffing or LCS usually story ends with Myers algorithm. It is idea to create BitMatrix (or IntMatrix) of similarities between two strings and then to approach to a problem from graph point, and find the shortest path of changes between two arrays. It sounds as a great solution, and it is, but in reality it has few drawbacks. It is algorithm that gives great results most of the time but it is a slow algorithm for some obvious cases, if strings are equal, or very similar. You create whole matrix but for your solution not all values from matrix are needed. It involves one more algorithm, finding the shortest path in it self. Then improvements of Myers algorithm came, like to check on start are strings are equals, to check for same prefix or suffix, to run snake chase from both