Grails - Hibernate Criteria Builder

Hibernate Criteria Builder

Description

A builder for creating criteria-based queries analogous to those found in the Hibernate Criteria API, the nodes on this builder map the the static methods found in the Restrictions class of the Hibernate Criteria API. Example Usage:

def c = Account.createCriteria()
def results = c {
	like("holderFirstName", "Fred%")
	and {
		between("balance", 500, 1000)
		eq("branch", "London")
	}
	maxResults(10)
	order("holderLastName", "desc")
}
As you can see, the mapping from Groovy "camel case" to the database's naming scheme (usually underscored instead of camel case) is handled transparently for you.

Setting properties in the Criteria instance

If a node within the builder tree doesn't match a particular criterion it will attempt to set a property on the Criteria object itself. Thus allowing full access to all the properties in this class. The below example calls "setMaxResults" and "setFirstResult" on the Criteria instance:

import org.hibernate.FetchMode as FM
....
def results = c.list {
	maxResults(10)
	firstResult(50)
	fetchMode("aRelationship", FM.EAGER)
}





Querying Associations

Associations can be queried by having a node that matches the property name. For example say the Account class had many Transaction objects:

class Account {
    …
    def hasMany = [transactions:Transaction]
    Set transactions
    …
}

We can query this association by using the property name "transaction" as a builder node:
def c = Account.createCriteria()
def now = new Date()
def results = c.list {
       transactions {
            between('date',now-10, now)
       }
}

The above code will find all the accounts that have performed transactions within the last 10 days. You can also nest such association queries within logical blocks:

def c = Account.createCriteria()
def now = new Date()
def results = c.list {
     or {
        between('created',now-10,now)
        transactions {
             between('date',now-10, now)
        }
     }
}





Here we find all accounts that have either performed transactions in the last 10 days OR have been recently created in the last 10 days.




Querying with Projections

Projections to be used to customise the results. To use projections you need to define a "projections" node within the criteria builder tree. There are equivalent methods within the projections node to the methods found in the Hibernate Projections class:

def c = Account.createCriteria()

def numberOfBranches = c.get { projections { countDistinct('branch') } }







Using Scrollable Results

You can use Hibernate's ScrollableResults feature by calling the scroll method:

def results = crit.scroll {
      maxResults(10)
}
def f = results.first()
def l = results.last()
def n = results.next()
def p = results.previous()

def future = results.scroll(10) def accountNumber = results.getLong('number')


To quote the documentation of Hibernate ScrollableResults:



A result iterator that allows moving around within the results by arbitrary increments. The Query / ScrollableResults pattern is very similar to the JDBC PreparedStatement/ ResultSet pattern and the semantics of methods of this interface are similar to the similarly named methods on ResultSet.

Contrary to JDBC, columns of results are numbered from zero.

Method Reference


If you invoke the builder with no method name such as:

c { … }
defaults to
c.list { … }

MethodDescription
listThis is the default method. It returns all matching rows.
getReturns a unique result set, i.e. just one row. The criteria has to be formed that way, that it only queries one row. This method is not to be confused with a limit to just the first row.
scrollReturns a scrollable result set
listDistinctIf subqueries or associations are used, one may end up with the same row multiple times in the result set. In Hibernate one would do a "CriteriaSpecification.DISTINCT_ROOT_ENTITY". In grails one can do it even simpler by just using this method.


Node Reference

NodeDescriptionExample
andLogical AND operator
and {
between("balance", 500, 1000)
eq("branch", "London")
}
betweenWhere the property value is between to distinct values
between("balance", 500, 1000)
eqWhere a property equals a particular value
eq("branch", "London")
eqPropertyWhere one property must equal another
eqProperty("lastTransaction","firstTransaction")
gtWhere a property is greater than a particular value
gt("balance",1000)
gtPropertyWhere a one property must be greater than another
gtProperty("balance","overdraft")
geWhere a property is greater than or equal to a particular value
ge("balance",1000)
gePropertyWhere a one property must be greater than or equal to another
geProperty("balance","overdraft")
idEqWhere an objects id equals the specified value
idEq(1)
ilikeA case-insensitive 'like' expression
ilike("holderFirstName","Steph%")
inWhere a one property is contained within the specified list of values
note: 'in' is a groovy reserve word, we must escape it by quotes.
'in'("holderAge",[18..65])
isEmptyWhere a collection property is empty
isEmpty("transactions")
isNotEmptyWhere a collection property is not empty
isNotEmpty("transactions")
isNullWhere a property is null
isNull("holderGender")
isNotNullWhere a property is not null
isNotNull("holderGender")
ltWhere a property is less than a particular value
lt("balance",1000)
ltPropertyWhere a one property must be less than another
ltProperty("balance","overdraft")
leWhere a property is less than or equal to a particular value
le("balance",1000)
lePropertyWhere a one property must be less than or equal to another
leProperty("balance","overdraft")
likeEquivalent to SQL like expression
like("holderFirstName","Steph%")
neWhere a property does not equals a particular value
ne("branch", "London")
nePropertyWhere one property does not equal another
neProperty("lastTransaction","firstTransaction")
notNegates an expression, logical NOT
not {
between("balance", 500, 1000)
} 
orLogical OR operator
or {
between("balance", 500, 1000)
eq("branch", "London")
} 
orderOrder the results by a particular property
order("holderLastName", "desc")
sizeEqWhere a collection property's size equals a particular value
sizeEq("transactions", 10)

Samples

Here is a sample of a search function inside of a controller:

def search = {
            if (request.method == 'POST'){
                def criteria = Network.createCriteria();

def results = criteria { and{ if(params.licenseId && params.licenseId != ''){ eq("licenseId",Integer.parseInt(params.licenseId)) } if(params.initialName && params.initialName != ''){ like("initialName", '%' + params.initialName + '%') } if(params.country.id){ eq("country.id",Long.parseLong(params.country.id)) } if(params.defaultOperator.id){ eq("defaultOperator.id",Long.parseLong(params.defaultOperator.id)) } if(params.networkStartDate_year && params.networkStartDate_month && params.networkStartDate_day){ String input = "${params.networkStartDate_year}/${params.networkStartDate_month}/${params.networkStartDate_day}" Date inputDate = new SimpleDateFormat("yyyy/MM/dd") .parse(input) between("networkStartDate",inputDate - 10, inputDate + 10) } if(params.technologyType){ like("technologyType", params.technologyType) } } firstResult(20) maxResults(20) order("licenseId") }

render(view:'list',model:[networkList : results]) } }