Search code examples
hibernategrailsgrails-orm

Grails GORM withCriteria Alias


I have set up a gorm criteria query using grails. There are some sqlProjections included. Now I need to order by this sqlProjections.

    def serviceList = Service.withCriteria {

        projections {
            property('id', 'id')
            property('lastServiceMileage', 'lastServiceMileage')
            property('nextServiceMileage', 'nextServiceMileage')
            sqlProjection('(SELECT MAX(e.mileage) FROM tbl_maxmileage AS e WHERE e.v_id = {alias}.v_id) AS mileage', ['mileage'], [INTEGER])
            sqlProjection('(ABS((SELECT mileage) - {alias}.last_service_mileage) / ({alias}.next_service_mileage - {alias}.last_service_mileage)) * 100  AS nextServiceInPercent', ['nextServiceInPercent'], [INTEGER])
            sqlProjection('{alias}.next_service_mileage - (SELECT mileage) AS nextServiceIn', ['nextServiceIn'], [INTEGER])
        }

        if (params.sort && params.order) {
            order(params.sort, params.order)
        }

        firstResult(params.int('offset'))
        maxResults(params.int('max'))

        setResultTransformer(Transformers.aliasToBean(Service.class))

    }

Hibernate fails and prints that eg. 'nextServiceIn' is not found if params.sort = nextServiceIn

I think it is because of the reason I have not written an alias infront of 'nextServiceIn'.

Now I need to know how to define an criteria root alias with 'withCriteria'

Using the session Object it could be done with

    hibernateSession.createCriteria(Service.class, "s");

Solution

  • I had a look to the raw query generated by hibernate. I noticed that 'nextServiceIn' is printed there without any alias as prefix.

    eg 'order by nextServiceIn asc' ...

    Now the solution is to extend org.hibernate.criterion.Order and implement a basic ordering without any lookup.

    import org.hibernate.criterion.Order
    import org.hibernate.criterion.CriteriaQuery
    import org.hibernate.Criteria
    import org.hibernate.HibernateException
    
    /**
     * Created with IntelliJ IDEA.
     * User: pheinrich
     * Date: 18.01.16
     * Time: 16:33
     * To change this template use File | Settings | File Templates.
     */
    public class OrderBySql extends Order {
    
        private String sqlOrderString;
    
        /**
         * Constructor for Order.
         * @param sqlOrderString an SQL order that will be appended to the resulting SQL query
         */
        protected OrderBySql(String sqlOrderString) {
            super(sqlOrderString, true);
            this.sqlOrderString = sqlOrderString;
        }
    
        public String toString() {
            return sqlOrderString;
        }
    
        public String toSqlString(Criteria criteria, CriteriaQuery criteriaQuery) throws HibernateException {
            return sqlOrderString;
        }
    
        /**
         * Custom order
         *
         * @param sqlProperty an SQL property that will be appended to the resulting SQL query
         * @param sqlOrder an SQL order that will be appended to the resulting SQL query
         * @return Order
         */
        public static Order sqlOrder(String sqlProperty, String sqlOrder) {
            return new OrderBySql(sqlProperty + " " + sqlOrder);
        }
    
    }
    

    And use it inside your GORM criteria

    def serviceList = Service.withCriteria {
    
        projections {
            property('id', 'id')
            property('lastServiceMileage', 'lastServiceMileage')
            property('nextServiceMileage', 'nextServiceMileage')
            sqlProjection('(SELECT MAX(e.mileage) FROM tbl_maxmileage AS e WHERE e.v_id = {alias}.v_id) AS mileage', ['mileage'], [INTEGER])
            sqlProjection('(ABS((SELECT mileage) - {alias}.last_service_mileage) / ({alias}.next_service_mileage - {alias}.last_service_mileage)) * 100  AS nextServiceInPercent', ['nextServiceInPercent'], [INTEGER])
            sqlProjection('{alias}.next_service_mileage - (SELECT mileage) AS nextServiceIn', ['nextServiceIn'], [INTEGER])
        }
    
        if (params.sort && params.order) {
    
            // special handling for sqlProjection alias
            if (params.sort == "nextServiceIn") {
                order(OrderBySql.sqlOrder(params.sort, params.order))
            } else {
                order(params.sort, params.order)
            }
    
        }
    
        firstResult(params.int('offset'))
        maxResults(params.int('max'))
    
        setResultTransformer(Transformers.aliasToBean(Service.class))
    
    }