venerdì 4 gennaio 2013

how to generate the hql query from the instance

In some cases we have the need to transform an object in an hql query considering only the valued properties, going in introspection also. For example, we have the following class:
class Bean{

   Integer id;
   String description;
   String other;

   // getter and setter..
}
The scope is to include in the HQL query only the valorized properties of this bean. then we immediately think of a cascade of if:
String hql = "Select Bean from Bean";
boolean isWhere = true;

if(bean.getId() != null) {
   if(isWhere){
      hql += " Where ";
      isWhere = false; 
   }else{
      hql += " and ";
   }
   hql += "Bean.id = " + bean.getId();
}

if(bean.getDescription() != null) {
   if(isWhere){
      hql += " Where ";
      isWhere = false; 
   }else{
      hql += " and ";
   }
   hql += "Bean.id = " + bean.getDescription();
}

// etc..
The result is a HQL query generated at runtime, but the code written isn't generic and you must in all cases write custom code. Follows an algorithm created by me, a recursive function that permits to write hql query in every situation:
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Date;

class Generate {

   private boolean isWhere;
 
   public String hql(Object obj){
        isWhere = true;
        String alias = ob.getClass().getSimpleName().toLowerCase();
        return hql(obj, alias, true);
   }

   private String hql(Object ob, String alias, boolean isFirst){
     
      Class<? extends Object> obClass = ob.getClass();
      StringBuilder sb = new StringBuilder();
  
      if (isFirst)
         sb.append("SELECT "+alias+" FROM "+obClass.getSimpleName()+" as "+alias);

      try {

         for (Field field : obClass.getDeclaredFields()) {
              
            Class<?> fieldType = field.getType();
            String fieldName = field.getName();
    
            if(!isCollection(fieldType) && !isSerialVersionUID(fieldName)){
 
               Object fieldValue = null;
   
               try{ fieldValue = obClass.getMethod(mGet(fieldName)).invoke(ob);
               }catch(Exception e){
                  // some fields don't have get method
                  // for example serialVersionUID    
               }
     
               if (fieldValue != null)

                  // if the field is complex type it goes into introspection
                  if (isFk(fieldType))
                     sb.append(hql(fieldValue, alias+"."+fieldName, false));
                  
                  else { 
                     if (isWhere) { 
                        sb.append(" WHERE ");
                        isWhere = false;
                     } else 
                        sb.append(" AND ");
   
                     sb.append(alias+"."+fieldName+" = "+getValue(fieldValue, fieldType));
                  }
             }
          }
      } catch (Exception e) { /** to handle **/ }

      return sb.toString();
   }
    
   private String getValue(Object value, Class<?> field) {

        StringBuilder sb = new StringBuilder();
 
        try { if (field.equals(String.class)
               || field.equals(Character.class)
               || field.equals(char.class))    sb.append("'"+value+"'");

              else
                 if(field.equals(Date.class))  sb.append("to_timestamp('"+value+"')");
                 else                          sb.append(value);
     
        } catch (Exception e) { /** to handle **/ }

        return sb.toString();
   }
 

   public static final ArrayList basicTypes = 
   
   new ArrayList(){
      private static final long serialVersionUID = -5567529960231273742L;

      {
        add(byte.class.getName());
        add(short.class.getName());
        add(int.class.getName());
        add(long.class.getName());
        add(float.class.getName());
        add(double.class.getName());
        add(char.class.getName());
        add(boolean.class.getName());
        add(Byte.class.getName());
        add(Short.class.getName());
        add(Integer.class.getName());
        add(Long.class.getName());
        add(Float.class.getName());
        add(Double.class.getName());
        add(Character.class.getName());
        add(Boolean.class.getName());
        add(String.class.getName());
        add(Date.class.getName());
      }
   };
 
   private boolean isCollection(Class<?> aClass){
      return Collection.class.isAssignableFrom(aClass);
   }
 
   private String mGet(String s) {
      return "get" + s.substring(0, 1).toUpperCase() + s.substring(1);
   }
 
   private boolean isSerialVersionUID(String field) {
      return field.equals("serialVersionUID");
   }

   private boolean isFk(Class<?> field) {
      return !basicTypes.contains(field.getName());
   }
} 
The exceptions aren't handled so to focalize the attention on the algorithm. An example of use:
   Bean bean = new Bean(2013,"description");
   
   String hql = new Generate().hql(bean);

   assertEqual("SELECT bean FROM Bean as bean WHERE bean.id = 2013 and bean.description = 'description'", hql);

1 commento:

  1. This is almost the same thing, but already built in Hibernate framework, and much more elegant. http://docs.jboss.org/hibernate/orm/3.3/reference/en/html/querycriteria.html#querycriteria-examples
    Anyway, for some special query types only hql can be used, so I understand why you did this. I had to do something similar but I took another approach; this is more generalized than what I did.

    RispondiElimina