These old forums are deprecated now and set to read-only. We are waiting for you on our new forums!
More modern, Discourse-based and with GitHub/Google/Twitter authentication built-in.

All times are UTC - 5 hours [ DST ]



Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 1 post ] 
Author Message
 Post subject: Query by Example
PostPosted: Sun May 15, 2011 10:57 pm 
Newbie

Joined: Sun May 15, 2011 10:12 pm
Posts: 1
Hi all
We have recently implemented some query by example functionality for hibernate. This post is aimed at seeing if there is already existing hibernate functionality which provides this feature.

now i realise that the following API exists
Code:
Example exampleUser = Example.create(u)
.ignoreCase() .enableLike(MatchMode.ANYWHERE) .excludeProperty("password");
return getSession().createCriteria(User.class) .add(exampleUser)
.list();

However the drawbacks of this method is that
1. We have to create a "special" empty version of out business objects since new User() would initialise some fields to not null values.
2. How can can i distinguish between null = "i dont care whats this value is" and null = "this value must be not set"
3. How do i create disjuctions where a property could be equal to several possible values.
4. Generally i just be more comfortable with being explicit about whats criteria im searching on rather than using a object state

So why not use explicit Critera you ask?
Code:
List rules = ((HibernateEntityManager)getEntityManager()).getSession().createCriteria(User.class)
       .add( Restrictions.eq("username", username) )
       .list();

I think having the "username" field as a hardcoded string is a problem here. Its another thing I need to get right and another thing that could go wrong if i refactor the property (to be called "userName" for example)

So..... Heres what we came up with
Code:
        Criteria add = ((HibernateEntityManager)getEntityManager()).getSession().createCriteria(User.class);
      
   User user = query(User.class);
   user.setUsername("username");
   return (((QueryByExample)user).populateCriteria(add)).list();

Basically we used some dynamic proxy hackery in the "query()" method to return a proxyed User object. This proxy records what "setProperty()" method invocations are being called as well as the arguments used. The proxy also dynamically impliments the QueryByExample interface which has the populate critera method which takes a criteria object and populates it with the all the criteria resulting from the methods invoked on the proxy.
so
1. The business object does not need to change in anyway to accomodate query by example. It doesnt need to extends or implement anything for that purpose
2. Im being explicit about what properties im searching on by invoking the specific setter properties. I dont have to guess/specify what criteria is important from looking a object state
3. I can easily query based on disjuctions by simple calling the same setter multiple times with different values

The proxy hackery looks like this. We have used ClassImposterizer from mockito
Code:
public static interface QueryByExample {
   Criteria populateCriteria(Criteria criteria);
}
   
protected static <T> T query(Class<T> clazz) {
       final T impostor = ClassImposterizer.INSTANCE.imposterise(new MethodInterceptor() {
       private QueryBuilder queryBuilder = new QueryBuilder();
           
       public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
            if (method.getName().equals("populateCriteria")) {
                return queryBuilder.populateCriteria((Criteria) args[0]);
            } else if (method.getName().startsWith("set")) {
                 String fieldName = method.getName().substring(3, 4).toLowerCase()+method.getName().substring(4);
                 queryBuilder.addField(fieldName, args[0]);
            }
            return null;
        }
        }, clazz, QueryByExample.class);
        return impostor;
    }
   
   static class QueryBuilder {
       private Map<String, List<Object>> values = new HashMap<String, List<Object>>();
       public void addField(String name, Object value) {
          if(values.get(name)==null)
             values.put(name, new ArrayList<Object>());
           values.get(name).add(value);
       }
      
       public Criteria populateCriteria(Criteria criteria) {
           for (Map.Entry<String, List<Object>> entry : values.entrySet()) {
              if(entry.getValue().size()==1)
                 criteria.add(Restrictions.eq(entry.getKey(), entry.getValue().get(0)));
              else
                 criteria.add(getDisjunction(entry.getKey(), entry.getValue()));
           }
           return criteria;
       }

      private Criterion getDisjunction(String key, List<Object> values) {
         Disjunction disjunction = Restrictions.disjunction();
         for(Object obj : values) {
            disjunction.add(Restrictions.eq(key, obj));
         }
         return disjunction;
      }
   }


Top
 Profile  
 
Display posts from previous:  Sort by  
Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 1 post ] 

All times are UTC - 5 hours [ DST ]


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum

Search for:
© Copyright 2014, Red Hat Inc. All rights reserved. JBoss and Hibernate are registered trademarks and servicemarks of Red Hat, Inc.