Groovy

Building GORM Criteria Queries Dynamically

I originally wrote most of the queries in the spring-security-ui plugin using HQL because I find it more intuitive than criteria queries, but HQL only works with Hibernate and relational databases. A pull request updated the queries to use criteria queries to allow the plugin to be used with NoSQL datastores, but one query didn’t fit the programming style that I was using. That wasn’t a big deal, but since a lot of the controller code is basically CRUD code and very similar to the others, I’ve tried to keep the code generic and push shared logic into the base classes.

The original HQL included this

hql.append " AND e.aclObjectIdentity.aclClass.id=:aclClass"

and the converted criteria code was

aclObjectIdentity {
   aclClass {
      eq 'id', params.long('aclClass')
   }
}

with the whole query being similar to this:

def results = lookupClass().createCriteria().list(max: max, offset: offset) {
   // other standard criteria method calls

   if (params.aclClass) {
      aclObjectIdentity {
         aclClass {
            eq 'id', params.long('aclClass')
         }
      }
   }
}

That got me thinking about creating a way to represent that two-level projection and criterion generically.

If we restore the omitted optional parentheses the code becomes

aclObjectIdentity({
   aclClass({
      eq('id', params.long('aclClass'))
   })
})

So it should be more clear that this is a sequence of method calls; calling aclObjectIdentity with a closure argument, then aclClass with a closure argument, and finally eq with a String and a long argument. Splitting out the closures as local variables makes it more clear, first as

def aclClassClosure = {
   eq('id', params.long('aclClass'))
}

aclObjectIdentity({
   aclClass(aclClassClosure)
})

and then

def aclClassClosure = {
   eq 'id', params.long('aclClass')
}

def aclObjectIdentityClosure = {
   aclClass(aclClassClosure)
}

aclObjectIdentity(aclObjectIdentityClosure)

To make this a bit more concrete, lets say we have three domain classes;

Department:

class Department {
   String name
}

Manager:

class Manager {
   String name
   Department department
}

and Employee:

class Employee {
   String name
   Manager manager
}

We create some instances:

Department d = new Department(name: 'department1').save()
Manager m = new Manager(name: 'manager1', department: d).save()
Employee e1 = new Employee(name: 'employee1', manager: m).save()
Employee e2 = new Employee(name: 'employee2', manager: m).save()

and later want to run a query:

Employee.createCriteria().list(max: 10, offset: 0) {
   eq 'name', 'employee1'

   manager {
      department {
         eq 'name', 'department1'
      }
   }
}

My goal is to represent this query with only some helper methods and without any closures (or as few as possible). Splitting that out like above we have

def departmentClosure = {
   eq 'name', 'department1'
}

def managerClosure = {
   department(departmentClosure)
}

def criteriaClosure = {
   eq 'name', 'employee1'

   manager(managerClosure)
}

Employee.createCriteria().list([max: 10, offset: 0], criteriaClosure)

When the query is run, the delegate of criteriaClosure is set to an instance of HibernateCriteriaBuilder when using Hibernate, or an analogous builder for MongoDB or whatever other GORM implementation you’re using. The builder has defined methods for eq, like, between, etc., so when you make those calls in your criteria closure they’re run on the builder.

It turns out that it works the same way if you split the closure into multiple closures and call them with the builder as the delegate for each. So a method like this works:

def runCriteria(Class clazz, List<Closure> criterias, Map paginateParams) {
   clazz.createCriteria().list(paginateParams) {
      for (Closure criteria in criterias) {
         criteria.delegate = delegate
         criteria()
      }
   }
}

and that means that we can split

Employee.createCriteria().list(max: 10, offset: 0) {
   eq 'name', 'employee1'

   manager {
      department {
         eq 'name', 'department1'
      }
   }
}

into

def closure1 = {
   eq 'name', 'employee1'
}

def closure2 = {
   manager {
      department {
         eq 'name', 'department1'
      }
   }
}

and run it as

runCriteria Employee, [closure1, closure2], [max: 10, offset: 0]

But how can we make that projection generic? It’s an inner method call, wrapped in one or more closures that project down to another domain class.

What I ultimately want is to be able to specify a projection with an inner criteria call without closures:

def projection = buildProjection('manager.department',
                                 'eq', ['name', 'department1'])
runCriteria Employee, [closure1, projection], [max: 10, offset: 0]

Here’s the buildProjection method that does this:

Closure buildProjection(String path, String criterionMethod, List args) {

   def invoker = { String projectionName, Closure subcriteria ->
      delegate."$projectionName"(subcriteria)
   }

   def closure = { ->
      delegate."$criterionMethod"(args)
   }

   for (String projectionName in (path.split('\\.').reverse())) {
      closure = invoker.clone().curry(projectionName, closure)
   }

   closure
}

To understand how this works, look again at the innermost closure:

department {
   eq 'name', 'department1'
}

This will be invoked as a method call on the delegate, in effect

delegate.department({
   eq 'name', 'department1'
})

Groovy lets us call methods dynamically using GStrings, so this is the same as

String methodName = 'department'

delegate."$methodName"({
   eq 'name', 'department1'
})

So we can represent the nested closures as an inner closure invoked as the closure argument of its containing closure, and that invoked as the closure argument of its containing closure, and so on until we run out of levels.

And we can build a closure that calls eq 'name', 'department1' (or any criterion method with arguments, this is just a simplified example), as

def closure = { ->
   delegate."$criterionMethod"(args)
}

So to represent the nested closures, start with an ‘invoker’ closure:

def invoker = { String projectionName, Closure subcriteria ->
   delegate."$projectionName"(subcriteria)
}

and successively clone it at each nesting level, and curry it to embed the projection name and its inner closure since the criteria builder doesn’t expect any closure arguments, working from the inside out:

for (String projectionName in (path.split('\\.').reverse())) {
   closure = invoker.clone().curry(projectionName, closure)
}

So, finally we can run the decomposed query as one or more ‘core’ criteria closures with standard criterion method calls, plus zero or more derived projection closures:

def criteria = {
   eq 'name', 'employee1'
}
def projection = buildProjection('manager.department',
                                 'eq', ['name', 'department1'])

runCriteria Employee, [criteria, projection], [max: 10, offset: 0]

I doubt there’s a lot of reuse potential here to be honest, but working through this helped me to better understand how GORM runs criteria queries. I’ll be talking about this and some other GORM topics at Greach next month, so if you find this interesting be sure to check out the recording of that talk.

Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Back to top button