Search This Blog

Friday 28 December 2012

Criterion

In our previous post we saw the Criteria API at its simplest. No select clauses or where clauses. Just the from clause. However without the ability to apply conditions, Criteria as a feature would be of very limited use.
public static void testLike() {
    final Session session = sessionFactory.openSession();
    Criteria criteria = session.createCriteria(Entity.class);
    criteria.add(Restrictions.like("name", "entity1"));
    List<Object> all = criteria.list(); 
    System.out.println(all);
}
The steps followed are as follows:
  1. Create a criteria instance from the session for Entity class
  2. To the Criteria instance add a criterion that represents the like expression for string comparison on the data column
  3. Execute the criteria and display the result.
But how does this actually work ?
Criterion is an interface which just two methods:
/**
 * Render the SQL fragment
 *
 * @param criteria The local criteria
 * @param criteriaQuery The overall criteria query
 *
 * @return The generated SQL fragment
 * @throws org.hibernate.HibernateException Problem during rendering.
 */
public String toSqlString(Criteria criteria, CriteriaQuery criteriaQuery) throws HibernateException;
    
/**
 * Return typed values for all parameters in the rendered SQL fragment
 *
 * @param criteria The local criteria
 * @param criteriaQuery The overal criteria query
 *
 * @return The types values (for binding)
 * @throws HibernateException Problem determining types.
 */
public TypedValue[] getTypedValues(Criteria criteria, CriteriaQuery criteriaQuery) throws HibernateException;
The first method will return the SQL fragment that is used in the query.The second one tells Hibernate of the data type for the parameters that may be present in the query.
The Hibernate library has defined quite a decent number of Criterion in the org.hibernate.criterion package.
One of the simplest ones and relevant to our example is the LikeExpression class. This is the criterion we have added in the above code. But where did we initialize it ?
The Restrictions class exposes a series of static methods that initialize the Criterion instance and return it to us. In our case:
public static SimpleExpression like(String propertyName, Object value) {
    return new SimpleExpression(propertyName, value, " like ");
}
The query creation process involves a CriteriaLoader class. This class works with another called the CriteriaQueryTranslator. It is this guy who calls the toSqlString method of the SimpleExpression instance:
public String toSqlString(Criteria criteria, CriteriaQuery criteriaQuery)
throws HibernateException {
    String[] columns = criteriaQuery.getColumnsUsingProjection(criteria, propertyName);
    Type type = criteriaQuery.getTypeUsingProjection(criteria, propertyName);
    StringBuffer fragment = new StringBuffer();
    if (columns.length>1) fragment.append('(');
    SessionFactoryImplementor factory = criteriaQuery.getFactory();
    int[] sqlTypes = type.sqlTypes( factory );
    for ( int i=0; i<columns.length; i++ ) {
        boolean lower = ignoreCase && 
                ( sqlTypes[i]==Types.VARCHAR || sqlTypes[i]==Types.CHAR );
        if (lower) {
            fragment.append( factory.getDialect().getLowercaseFunction() )
                .append('(');
        }
        fragment.append( columns[i] );
        if (lower) fragment.append(')');
        fragment.append( getOp() ).append("?");
        if ( i<columns.length-1 ) fragment.append(" and ");
    }
    if (columns.length>1) fragment.append(')');
    return fragment.toString();
}
I have highlighted the code where the actual SQL fragment is replaced based on the Dialect. The question marks are also added here. This is how each Criterion is converted into the actual SQL string. Unlike HQL which involves creating Syntax trees from the user's HQL before converting into SQL, here the query is generated through function calls. Also I do not see any caching of the Criteria processing as we have in HQL - the QueryPlanCache.

No comments:

Post a Comment