6.4.3 Criteria - Reference Documentation
Authors: Graeme Rocher, Peter Ledbrook, Marc Palmer, Jeff Brown, Luke Daley, Burt Beckwith, Lari Hotari
Version: 3.0.11
6.4.3 Criteria
Criteria is an advanced way to query that uses a Groovy builder to construct potentially complex queries. It is a much better approach than building up query strings using aStringBuffer
.Criteria can be used either with the createCriteria or withCriteria methods. The builder uses Hibernate's Criteria API. The nodes on this builder map the static methods found in the Restrictions class of the Hibernate Criteria API. For example:def c = Account.createCriteria() def results = c { between("balance", 500, 1000) eq("branch", "London") or { like("holderFirstName", "Fred%") like("holderFirstName", "Barney%") } maxResults(10) order("holderLastName", "desc") }
Account
objects in a List matching the following criteria:
balance
is between 500 and 1000branch
is 'London'holderFirstName
starts with 'Fred' or 'Barney'
holderLastName
.If no records are found with the above criteria, an empty List is returned.Conjunctions and Disjunctions
As demonstrated in the previous example you can group criteria in a logical OR using anor { }
block:or { between("balance", 500, 1000) eq("branch", "London") }
and { between("balance", 500, 1000) eq("branch", "London") }
not { between("balance", 500, 1000) eq("branch", "London") }
Querying Associations
Associations can be queried by having a node that matches the property name. For example say theAccount
class had many Transaction
objects:class Account {
…
static hasMany = [transactions: Transaction]
…
}
transactions
as a builder node:def c = Account.createCriteria()
def now = new Date()
def results = c.list {
transactions {
between('date', now - 10, now)
}
}
Account
instances 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)
}
}
}
Querying with Projections
Projections may be used to customise the results. Define a "projections" node within the criteria builder tree to use projections. 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') } }
SQL Projections
The criteria DSL provides access to Hibernate's SQL projection API.// Box is a domain class… class Box { int width int height }
// Use SQL projections to retrieve the perimeter and area of all of the Box instances… def c = Box.createCriteria()def results = c.list { projections { sqlProjection '(2 * (width + height)) as perimeter, (width * height) as area', ['perimeter', 'area'], [INTEGER, INTEGER] } }
sqlProjection
method is the SQL which defines the projections. The second argument is a list of
Strings which represent column aliases corresponding to the projected values expressed in the SQL. The third argument
is a list of org.hibernate.type.Type
instances which correspond to the projected values expressed in the SQL. The API
supports all org.hibernate.type.Type
objects but constants like INTEGER, LONG, FLOAT etc. are provided by the DSL which
correspond to all of the types defined in org.hibernate.type.StandardBasicTypes
.Consider that the following table represents the data in the
BOX
table.
width | height |
---|---|
2 | 7 |
2 | 8 |
2 | 9 |
4 | 9 |
[[18, 14], [20, 16], [22, 18], [26, 36]]
Box
, perimeter and area.Note that if there are other references in scope wherever your criteria query is expressed that have names that conflict with any of the type constants described above, the code in your criteria will refer to those references, not the type constants provided by the DSL. In the unlikely event of that happening you can disambiguate the conflict by referring to the fully qualified Hibernate type. For exampleIf only 1 value is being projected, the alias and the type do not need to be included in a list.StandardBasicTypes.INTEGER
instead ofINTEGER
.
def results = c.list { projections { sqlProjection 'sum(width * height) as totalArea', 'totalArea', INTEGER } }
Box
instances.The DSL supports grouped projections with the sqlGroupProjection
method.def results = c.list { projections { sqlGroupProjection 'width, sum(height) as combinedHeightsForThisWidth', 'width', ['width', 'combinedHeightsForThisWidth'], [INTEGER, INTEGER] } }
sqlGroupProjection
method is the SQL which defines the projections. The second argument represents the
group by clause that should be part of the query. That string may be single column name or a comma separated list of column
names. The third argument is a list of
Strings which represent column aliases corresponding to the projected values expressed in the SQL. The fourth argument
is a list of org.hibernate.type.Type
instances which correspond to the projected values expressed in the SQL.The query above is projecting the combined heights of boxes grouped by width and would return results that look like this:[[2, 24], [4, 9]]
Using SQL Restrictions
You can access Hibernate's SQL Restrictions capabilities.def c = Person.createCriteria()def peopleWithShortFirstNames = c.list {
sqlRestriction "char_length(first_name) <= 4"
}
def c = Person.createCriteria()def peopleWithShortFirstNames = c.list {
sqlRestriction "char_length(first_name) < ? AND char_length(first_name) > ?", [maxValue, minValue]
}
Note that the parameter there is SQL. Thefirst_name
attribute referenced in the example refers to the persistence model, not the object model like in HQL queries. ThePerson
property namedfirstName
is mapped to thefirst_name
column in the database and you must refer to that in thesqlRestriction
string.Also note that the SQL used here is not necessarily portable across databases.
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')
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.
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. This allows full access to all the properties in this class. This example callssetMaxResults
and setFirstResult
on the Criteria instance:import org.hibernate.FetchMode as FM … def results = c.list { maxResults(10) firstResult(50) fetchMode("aRelationship", FM.JOIN) }
Querying with Eager Fetching
In the section on Eager and Lazy Fetching we discussed how to declaratively specify fetching to avoid the N+1 SELECT problem. However, this can also be achieved using a criteria query:def criteria = Task.createCriteria()
def tasks = criteria.list{
eq "assignee.id", task.assignee.id
join 'assignee'
join 'project'
order 'priority', 'asc'
}
join
method: it tells the criteria API to use a JOIN
to fetch the named associations with the Task
instances. It's probably best not to use this for one-to-many associations though, because you will most likely end up with duplicate results. Instead, use the 'select' fetch mode:
import org.hibernate.FetchMode as FM … def results = Airport.withCriteria { eq "region", "EMEA" fetchMode "flights", FM.SELECT }
flights
association, you will get reliable results - even with the maxResults
option.An important point to bear in mind is that if you include associations in the query constraints, those associations will automatically be eagerly loaded. For example, in this query:fetchMode
andjoin
are general settings of the query and can only be specified at the top-level, i.e. you cannot use them inside projections or association constraints.
def results = Airport.withCriteria { eq "region", "EMEA" flights { like "number", "BA%" } }
flights
collection would be loaded eagerly via a join even though the fetch mode has not been explicitly set.Method Reference
If you invoke the builder with no method name such as:c { … }
c.list { … }
Method | Description |
---|---|
list | This is the default method. It returns all matching rows. |
get | Returns 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. |
scroll | Returns a scrollable result set. |
listDistinct | If subqueries or associations are used, one may end up with the same row multiple times in the result set, this allows listing only distinct entities and is equivalent to DISTINCT_ROOT_ENTITY of the CriteriaSpecification class. |
count | Returns the number of matching rows. |
Combining Criteria
You can combine multiple criteria closures in the following way:def emeaCriteria = { eq "region", "EMEA" }def results = Airport.withCriteria { emeaCriteria.delegate = delegate emeaCriteria() flights { like "number", "BA%" } }
Airport
).
A more flexible approach is to use Detached Criteria, as described in the following section.