-->
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.  [ 6 posts ] 
Author Message
 Post subject: saving a criteria query?
PostPosted: Thu Sep 09, 2004 7:55 am 
Newbie

Joined: Thu Sep 09, 2004 6:50 am
Posts: 15
i am new to hibernate.

i have a use-case where i need to give the user the ability to create a query based on just about any property. the criteria query works like a charm.

however, i also need to be able to save the query that is created for future execution (ie, the user uses a front-end to buid the query once, and then can run it any time).

i can't seem to find an easy way to do this? the only approach that i can even think of is to somehow serialize the criteria and put it in the database table as a blob, but that sounds complicated and dangerous (since you create criteria within a session)

another way would be to save the map of property names/values/comparison types (greater than, less than, equal, like, etc) and then re-build the criteria each time i need it. this too sounds tedious.

can anyone offer a suggestion? am i missing something easy?

thanks so much!

maulin


Top
 Profile  
 
 Post subject:
PostPosted: Thu Sep 09, 2004 9:13 am 
Hibernate Team
Hibernate Team

Joined: Tue Aug 26, 2003 3:00 pm
Posts: 1816
Location: Austin, TX
Have you considered using a query-by-example? You could construct a net.sf.hibernate.expression.Example and then store that. Would depend upon how complex your search criteria is.


Top
 Profile  
 
 Post subject:
PostPosted: Thu Sep 09, 2004 9:29 am 
Newbie

Joined: Thu Sep 09, 2004 6:50 am
Posts: 15
i had tried using query by example, but its probably too restrictive.

also, even if i did QBE, how would i store the example? persist the source object of the example? that would be ugly because i'd be mixing "fake query object data" with "real" data. so then i would have to create a parallel "query object data" set of mappings and objects to avoid it (or subclass or something special)... yuck again...

i have got to be missing something.


Top
 Profile  
 
 Post subject:
PostPosted: Mon Sep 13, 2004 4:39 pm 
Newbie

Joined: Thu Sep 09, 2004 6:50 am
Posts: 15
ok, i have an implementation i am pretty satisfied with for now. it handles as much flexibility as i need.

i've created two classes -- storedquery and storedcriteria.

a stored query has a set of storedcriteria, as well as the name of the"root class" of the criteria query.

the storedcriteria consist of a "fieldname", and a "value", and the expression type (ilike, like, eq, etc)

the field name is in a ognl-like format of "class.property.property.property..." for example
user.name.lastname

the storedquery class has a method "compileQuery" that returns a criteia object ready to be executed.

compile query creates the base criteria with the session and the root class. it then calls "addCriteria" for each storedcriteria in its set.

"addCriteria" splits the field name and creates a nested criteria from the parent until it finally gets to a simple field (ie, no more "." in the field string) and then it calls createExpression, which it adds to the current criteria, and it bubbles back up to the caller.

i don't if i have made this clear, but i've pasted some very ugly code below for anyone interested. this is a quick fiorst draft and needs work, but its a start...

eventually i'll adapt it to use ognl or something but my first draft just parses the string. right now i can only do conjunction, and eventually i'd like to add conjunction, but again, this was just a first cut.

by the way, this makes for a pretty simple UI with tapestry. i create a "build query" page and add fields with a dropdown, expresison type with a dropdown, and values with a text box.


Code:

/**
* @author Maulin Shah
*
* @hibernate.class table="storedqueries"
* @hibernate.discriminator column = "rootClass"
* @hibernate.query name="findAllStoredApplicantQueries"
*          query="from StoredApplicantQuery"
*
*
*/
public class StoredQuery extends RAMObject {

    private Set queryPermissions;
    private String name;
    private Set criteria;
    private Set ordering;
    private String rootClass;
   
   
    public Criteria createCriteria(Criteria parentCriteria, StoredCriteria currentCriteria, String currentFieldName) throws RAMException{
     
          try {
               if (parentCriteria == null) return null;
               if (currentCriteria == null) return parentCriteria;
           
               
               
               
               if (currentFieldName.indexOf(".") >=0 ) {
                   //then get the first token
                   //and send it as the parent
                   String[] tokens = currentFieldName.split("\\.");
                  
                   if (tokens.length <= 1) throw new RAMException("expected at least two tokens on " + currentFieldName);
                  
                   //the remainder of the string needs to be processed
                   String remainder ="";
                   for (int i=1; i<tokens.length; i++) {
                       if (remainder.length() > 0)
                           remainder += ".";
                       remainder+=tokens[i];
                   }
                  
                   //recursive
                   createCriteria(parentCriteria.createCriteria(tokens[0]), currentCriteria, remainder); 
                  
               }
               else {   
                  
                   Criterion criterion = currentCriteria.generateCriterion(currentFieldName);
                   parentCriteria.add(criterion);
               }
         
          } catch (HibernateException e) {
              e.printStackTrace();
             
              throw new RAMException(e);
          }
            return parentCriteria;
           
       
           
    }
   
    public Criteria getCompiledCriteria() throws RAMException {
        try {
            Class queryClass = Class.forName(rootClass);
            Session session = HibernateSessionFactory.currentSession();
            Criteria crit = session.createCriteria(queryClass);
           
            for (Iterator i=criteria.iterator(); i.hasNext();) {
                StoredCriteria currentCriteria = (StoredCriteria) i.next();
                crit = createCriteria(crit, currentCriteria, currentCriteria.getField() );         
            }
            crit.setResultTransformer(Criteria.ROOT_ENTITY);
            return crit;
        }  catch (ClassNotFoundException e) {
            e.printStackTrace();
            throw new RAMException("the root class supplied in the data is not valid!", e);
        }
           
         
       
   
    }
   
    public List getQueryResults() throws RAMException {
        try {
            Criteria crit = getCompiledCriteria();
            return crit.list();
        } catch (HibernateException e) {
            e.printStackTrace();
            throw new RAMException(e);
        }
    }

    /**
     * @hibernate.property
     * @return Returns the rootClass.
     */
    public String getRootClass() {
        return this.rootClass;
    }
    /**
     * @param rootClass The rootClass to set.
     */
    public void setRootClass(String rootClass) {
        this.rootClass = rootClass;
    }
    /**
    *  @hibernate.set table="storedcriteria" inverse="true"  cascade="all"
     * @hibernate.collection-key column="query"
     * @hibernate.collection-one-to-many class="org.baylormedicine.ram.storedqueries.StoredCriteria"
     *     
     * @return Returns the criteria.
     */
    public Set getCriteria() {
        return this.criteria;
    }

    /**
     * @param criteria The criteria to set.
     */
    public void setCriteria(Set criteria) {
        this.criteria = criteria;
    }

    /**
    *  @hibernate.set table="storedordering" inverse="true"  cascade="all"
     * @hibernate.collection-key column="query"
     * @hibernate.collection-one-to-many class="org.baylormedicine.ram.storedqueries.StoredOrdering"
     *     
     * @return Returns the ordering.
     */
    public Set getOrdering() {
        return this.ordering;
    }

    /**
     * @param ordering The ordering to set.
     */
    public void setOrdering(Set ordering) {
        this.ordering = ordering;
    }

    /**
     * @hibernate.set table="queryPermissions" inverse="true"  cascade="all"
     * @hibernate.collection-key column="query"
     * @hibernate.collection-one-to-many class="org.baylormedicine.ram.frontend.QueryPermission"
     * @return Returns the userGroups.
     */
    public Set getQueryPermissions() {
        return this.queryPermissions;
    }

    /**
     * @param userGroups The userGroups to set.
     */
    public void setQueryPermissions(Set queryPermissions) {
        this.queryPermissions = queryPermissions;
    }

    /**
     * @hibernate.property
     * @return Returns the name.
     */
    public String getName() {
        return this.name;
    }

    /**
     * @param name The name to set.
     */
    public void setName(String name) {
        this.name = name;
    }

}

-----------------------------------------------------------------------------------


*
* @hibernate.class table="storedcriteria"
*/
public class StoredCriteria extends RAMObject {
    private String value;
    private String valueType;
    private String field;
    private StoredQuery query;
    private String expression;
    private String matchModeString;
   
   
   
    private MatchMode getMatchMode(){
        Class matchModeClass = MatchMode.class;
       try {
           
           Field field = matchModeClass.getField(matchModeString);
           return (MatchMode) field.get(null);
           
       } catch (Exception e) {
           e.printStackTrace();
           return null;
       }
       
    }
   
    public Object castedValue() {
        if (valueType==null) return value;
        if (valueType.equals("java.lang.Integer")) return Integer.valueOf(value);
        return value;
    }
   
    public Criterion generateCriterion() {
        return generateCriterion(field);
    }
   
    public Criterion generateCriterion(String fieldName) {
        Class expressionClass = Expression.class;
        try {
            Class[] args = new Class[] {String.class, Object.class};
           
            Method expressionMethod = expressionClass.getMethod(expression, args);
           
            Object[] argValues = new Object[] {fieldName, castedValue()};
            return (Criterion) expressionMethod.invoke(null, argValues);
           
           
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
       
    }
   
   
    /**
     * @hibernate.property
     * @return Returns the expression.
     */
    public String getExpression() {
        return this.expression;
    }
    /**
     * @param expression The expression to set.
     */
    public void setExpression(String expression) {
        this.expression = expression;
    }
    /**
     * @hibernate.property
     * @return Returns the field.
     */
    public String getField() {
        return this.field;
    }
    /**
     * @param field The field to set.
     */
    public void setField(String field) {
        this.field = field;
    }

    /**
    * @hibernate.many-to-one column="query" class="org.baylormedicine.ram.storedqueries.Query" not-null="true"
     * @return Returns the query.
     */
    public StoredQuery getQuery() {
        return this.query;
    }
    /**
     * @param query The query to set.
     */
    public void setQuery(StoredQuery query) {
        this.query = query;
    }
    /**
     * @hibernate.property
     * @return Returns the value.
     */
    public String getValue() {
        return this.value;
    }
    /**
     * @param value The value to set.
     */
    public void setValue(String value) {
        this.value = value;
    }
    /**
     * @return Returns the valueType.
     */
    public String getValueType() {
        return this.valueType;
    }
    /**
     * @param valueType The valueType to set.
     */
    public void setValueType(String valueType) {
        this.valueType = valueType;
    }
    /**
     * @hibernate.property
     * @return Returns the matchModeString.
     */
    public String getMatchModeString() {
        return this.matchModeString;
    }
    /**
     * @param matchModeString The matchModeString to set.
     */
    public void setMatchModeString(String matchMode) {
        this.matchModeString = matchMode;
    }
}




Top
 Profile  
 
 Post subject:
PostPosted: Mon Sep 13, 2004 5:19 pm 
Newbie

Joined: Thu Sep 09, 2004 6:50 am
Posts: 15
so you might ask why i didn't just create the criteria using the whole path, just skimming off the last bit as the expression. because 1) i just figured out you could do that (doh!) and 2) i'm gonna need to do this for ordering anyway, since you cannot order by path.


Top
 Profile  
 
 Post subject:
PostPosted: Mon Sep 20, 2004 3:55 pm 
Newbie

Joined: Thu Sep 09, 2004 6:50 am
Posts: 15
just as an update: the above implementation was clearly a brainstorm, and the devil has been in the details. i now have this implemented and working almost exactly as i want it to, and it is quite flexible. i've added a "set" concept that wraps the conjunction/disjunction to allow for complex and/or clauses. the only problem i have now is the problem with criteria and inner joins.

evidently every time you add an alias, it INNER JOIN's the alias to the query. so you can lose rows if your not thinking about it.

for example:

i have a set of applicants to a school.
each applicant has an address, a set of tests with scores, and a set of experiences (like previous work).

i want to build a criteria that says

"get me all the applicants who either
1) live in houston
2) have test scores > 95%
3) have previous experience"

this will skip anyone who does not have any experiences (since it tries to inner join to experiences, and then i lose all those rows). even if those people happen to meet one of the other two criteria. in SQL, this is easly to solve. actually, its easy in HQL as well. however, building a web-GUI for end-users to define the above query via HQL would be a big pain. It seems to me that this is the whole point of the criteria API.

there have been a couple other threads about this, even an unrelated thread in JiRA that mentions this as an aside, but i've seen no concrete solutions. anyone have any ideas?

by the way, here is my "storedquery" code if anyone wants it.

Code:
    public Criteria getCompiledCriteria(boolean keepSessionOpen) throws RAMException {

      Session session = HibernateSessionFactory.currentSession();
      try {
       
         Class queryClass = Class.forName(rootClass);
       
        Criteria masterCriteria = session.createCriteria(queryClass);
        aliases.clear();
        Junction currentJunction = null;
       
        StoredQueryStep currentStep = firstStep;
        Stack junctions = new Stack();
       
        while (currentStep != null) {
            switch (currentStep.getCommand()) {
               case StoredQueryStep.CLOSE_SET:
                   if (junctions.size() == 0) throw new RAMInvalidQueryException("you closed a set when there were no sets left to be closed");
                   
                    Junction junctionToClose = (Junction) junctions.pop();
                    if (junctions.size() > 0) {
                      Junction nextJunction = (Junction) junctions.peek();
                      nextJunction.add(junctionToClose);
                  } else {
                      masterCriteria.add(junctionToClose);
                  }
                   break;
               
                case StoredQueryStep.OPEN_SET_WITH_AND:
                   Junction newAndJunction = Expression.conjunction();
                   junctions.push(newAndJunction);
                   break;
                   
                case StoredQueryStep.OPEN_SET_WITH_OR:
                   Junction newOrJunction = Expression.disjunction();
                   junctions.push(newOrJunction);
                   break;
                   
                case StoredQueryStep.PROCESS_GROUP:
                    
            //cannot process without a set having been opened first
                    if (junctions.size()==0) throw new RAMInvalidQueryException("you tried to process a criteriagroup before opening a set");
                    
                   currentJunction = (Junction)junctions.peek(); //don't take it off the stack
                   
                   //we should have a valid currentJunction by now
                  if (currentJunction == null) throw new RAMInvalidQueryException("Invalid nesting in query. check your open and close set statements!");
                 
                  CriteriaGroup currentGroup = currentStep.getCriteriaGroup();
                 
                  if (currentGroup ==null) throw new RAMInvalidQueryException("you must specify a criteria group for each 'processing' step");
                 
                  Junction innerJunction = (currentGroup.getCombinationType()==CriteriaGroup.AND) ?
                          (Junction) Expression.conjunction() : (Junction) Expression.disjunction();
                     
                 for (Iterator i=currentGroup.getCriteria().iterator(); i.hasNext();) {
                     StoredCriteria currentStoredCriteria = (StoredCriteria) i.next();
                     createCriteria(masterCriteria, innerJunction, currentStoredCriteria);
                 }
                
                 currentJunction.add(innerJunction);
                 break;
            }
                
         currentStep = currentStep.getNextStep();
          
        }
        //close all the junctions that are left on the stack
        //this in case people are lazy about closing sets
        while (junctions.size() > 0) {
              currentJunction=(Junction) junctions.pop();
              if (junctions.size() > 0) {
                  Junction nextJunction = (Junction) junctions.peek();
                  nextJunction.add(currentJunction);
              }
              else
                  masterCriteria.add(currentJunction);
              
          
        }
       
        masterCriteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
       
       
       
        return masterCriteria;
    }  catch (ClassNotFoundException e) {
        e.printStackTrace();
        throw new RAMException("the root class supplied in the data is not valid!", e);
    } finally {
        HibernateSessionFactory.closeSession(keepSessionOpen);
    }
}

    private String getNextAliasName(String suffix) {
        return "alias" + Integer.toString(aliasIndex++) + suffix;
    }
   
    private String createAlias(String path, Criteria criteria) throws HibernateException{
        return _createAlias(path, criteria, "", path, 0);
    }
   
    private String getCurrentPath(String path, int index) {
        if (path.indexOf(".")<0) return path;
        String[] tokens = path.split("\\.");
        String ret = "";
       
        for (int i=0; i<=index; i++) {
            if (ret.length() > 0) ret+= ".";
            ret+=tokens[i];
        }
       
        return ret;
    }
   
    private String _createAlias (String path, Criteria criteria, String currentAlias, String originalPath, int currentIndex) throws HibernateException{
        String aliasName = null;
        //try to find the biggest matching alias?
         String currentPath = getCurrentPath(originalPath, currentIndex);
        
       if (path.indexOf(".") >=0 ) {
           //then get the first token
           //and send it as the parent
           String[] tokens = path.split("\\.");
          
          
           aliasName = (String) aliases.get(currentPath);
          
          
           if (aliasName == null) {
               String pathToHead;
               //make an alias for the head
               aliasName = getNextAliasName(tokens[0]);
               if (currentAlias.length()>0)
                   pathToHead = currentAlias + "." + tokens[0];
               else
                   pathToHead = tokens[0];
              
               criteria.createAlias(pathToHead, aliasName);
               criteria.setFetchMode(currentPath, FetchMode.EAGER);
               aliases.put(currentPath, aliasName);
           }
          
          
           //the remainder of the string needs to be processed
           String remainder ="";
           for (int i=1; i<tokens.length; i++) {
               if (remainder.length() > 0)
                   remainder += ".";
               remainder+=tokens[i];
           }
          
           //recursive
           return _createAlias(remainder, criteria, aliasName, originalPath, currentIndex+1);
       }
       else {   
          //we are at a "leaf"
           aliasName = (String) aliases.get(currentPath);
           if (aliasName==null) {
              aliasName = getNextAliasName(path);
              if (currentAlias.length() >0) currentAlias += ".";
              criteria.createAlias(currentAlias + path, aliasName);
              criteria.setFetchMode(currentPath, FetchMode.EAGER);
              aliases.put(currentPath, aliasName);
           }
       }
       
       return aliasName;
    }
   
    public Criteria createCriteria(Criteria masterCriteria, Junction currentJunction, StoredCriteria currentCriteria) throws RAMException{
           
            try {
                 if (masterCriteria == null) return null;
                 if (currentCriteria == null) return masterCriteria;
                 
                 
                 
                 String path = currentCriteria.getPath();
                 
                 Criterion criterion = null;
                 
                 if (path!=null) {
                     String aliasName = ((CriteriaImpl) masterCriteria).getAlias(path);
                     //String aliasName = (String) aliases.get(path);
                     if (aliasName==null) {
                        
                        aliasName = createAlias(path, masterCriteria);
                        aliases.put(path, aliasName);
                     }
                    
                   criterion = currentCriteria.generateCriterion(aliasName + "." + currentCriteria.getFieldName());
                
                 }
                 else {   
                     criterion = currentCriteria.generateCriterion(currentCriteria.getFieldName());
                 }
                 
                 if (criterion != null)
                   currentJunction.add(criterion);
                 
                 return masterCriteria;
           
            } catch (HibernateException e) {
                e.printStackTrace();
               
                throw new RAMException(e);
            }
             
             
         
             
      }





there are some more details, if you'd like to see all the code, just let me know (maulin AT houston DOT rr DOT com)


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

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.