Saturday, January 4, 2014

Grails: Using Hibernate Filters

The Grails Hibernate Filters plugin makes it possible to use Hibernate Filters with GORM. Hibernate Filters provide additional restriction rules that can be applied to classes and collections. The Hibernate Filters plugin makes it possible to use this functionality with GORM inside Grails applications. Let's look at an example to see how filters can help us.

Assume we have the following Grails domain class:
class User {
  String username
  boolean locked
}
The locked flag indicates if an User has been locked by an administrator for some reason. Assume that most parts of our application should treat locked users as they would not exist. To accomplish this we could add an additional condition that takes care of the locked flag to all queries that are used to retrieve User objects from the database. However, this would not be a good solution for various reasons (think of DRY, what if we have more than one flag?). Hibernate filters can be a nice help in this situation.

Plugin installation
To install the Hibernate Filters Plugin in a Grails application we have to do two things:

Add the plugin dependency to BuildConfig.groovy:
compile ":hibernate-filter:0.3.2" 
Add the following configuration property to our dataSource definition:
dataSource {
  ...
  configClass = HibernateFilterDomainConfiguration
}
Filter configuration
Now we can add filters to our domain class using the static hibernateFilters property:
class User {
  String username
  boolean locked
  static hibernateFilters = {
    lockedFilter(condition: 'locked=0', default: true)
  }
}
Here we define a filter with the name lockedFilter. The filter is enabled by default which means that the condition will be always applied when we query the database for User objects.
// returns only users that are not locked
List users = User.findAll() 

// returns null if the user is locked
User john = User.findByUsername('John') 
However there is one exception: User.get(id) will return the User object even if the user for the given id has been locked. To apply the filter when retrieving a User by id we have to use  User.findById(id).

In some situations we might need all users, even the locked ones (for example in the administration interface). Here we can use the withoutHibernateFilter method to disable our filter:
User.withoutHibernateFilter('lockedFilter') {
  // get all users, including locked users
  List allUsers = User.findAll()
}
Filtering collections
Hibernate Filters can also be used to filter collections. Assume users are able to upload images in our application. These images are saved in a collection in our User domain class:
class User {
  ...
  static hasMany = [images: Image]
}
class Image {
  boolean visible
  static belongsTo = [user: User]
}
Administrators are able to hide inappropriate images by setting the visible property of Image to false. Images with the visible flag set to false should not be used anywhere (similar to locked users). The problem we face here is that user.getImages() returns all images of a given user by default.

Filters can help us here again:
class User {
  ...
  static hasMany = [images: Image]
  static hibernateFilters = {
    ...
    imagesVisibleFilter(collection: 'images', condition: 'visible=1', default: true)
  }
}
Here we define a filter named imagesVisibleFilter that is applied to the images collection by default. The filter condition will be applied whenever the images of a given user are retrieved from the database. If we now access the images collection using user.getImages() we only get visible images.

Conclusion
The Hibernate Filters plugin provides some nice and easy utilities for adding additional restrictions to domain classes and collections. The possibility to enable filters by default is a nice improvement compared to standard Hibernate in which filters need to be enabled manually for each Session instance.

You can find the example project I created while writing this post on GitHup.

Share this post using Facebook, Twitter or Google+

1 comment:

  1. "The filter is enabled by default which means that the condition will be always applied when we query the database for User objects." - is it true? What if I'll query for another object? Will condition be applied? What if for this condition I've got no 'locked' property in another object?

    ReplyDelete