Menu

Official website

Play Framework 1.2 - Writing a Multitenancy Application with Hibernate Filters


04 Mar 2011

min read

This article describes a method to simplify the construction of a Play 1.2 application with multiple users that have little or no shared data, using Hibernate Filters.

A multitenancy application is characterized by having multiple users that have independent data in the system. An example could be a todo list application, where every user has his own todo lists and tasks, and there is no relation to lists or tasks of other users of the system.

Conventional solution

Various methods can be used to separate the data of the users. You can use different database servers or different database instances within the same database server. For maintenance reasons however it is often preferable to keep all the data together and use a single database. In this case, every record in the database, and every database mapped Java class, must have a property that keeps track of the user to which it belongs, like in the following example:

public class Task extends Model {
    public String description;
    public Boolean done;

    @ManyToOne
    public User user;
}

On the other side of the relationship, we have the User class:

@Entity
public class User extends Model {
    public String name;

    @OneToMany(mappedBy="user")
    public Set<Task> tasks;
}

Now, to get a list of all the Tasks for the user that is logged in, we must do something like:

List<Task> tasks = Task.find("byUser", loggedInUser).fetch();

And to search for Tasks for the user that is logged in and that are not done yet, we can do:

List<Task> tasks = Task.find("byUserAndDone", loggedInUser, false).fetch();

What we see is that in every query we have to explicitly specify the user, which can become very cumbersome. This is especially true when you have a large amount of models that are all tied to a single user.

Hibernate Filters

You can improve on the situation by using Hibernate Filters. This feature of Hibernate allows you to automatically add extra WHERE clauses to every query that queries a specific table. It is slightly similar to creating a view on a table, but it is easier to manage from your application, easier to apply to multiple tables and it scales better, as you don’t have to explicitly define views for every user.

Hibernate filters consist of two annotations. The first is an @FilterDef annotation that defines the filter. The second is an @Filter annotation that applies the filter to the class the annotation is set on. A filter only has to be defined once, and can be applied to many entities.

We want to use the filter in the entire package, so we specify them on a package level in in the file package-info.java, residing in the models package:

@FilterDef(name="user", parameters= { @ParamDef( name="user_id", type="long") }, defaultCondition=":user_id = user_id", )
package models;

import org.hibernate.annotations.FilterDef;
import org.hibernate.annotations.ParamDef;

Here we have defined a filter with the name user, and one parameter user_id of type long. We have also specified a default condition. This condition can be overridden each time the filter is applied, but this is a good default for the cases where you just have a user member in an entity class, like in our Task.

It should also be possible to just define this filter on an arbitrary entity class, but a bug in Hibernate may cause you trouble in that case.

Applying a Filter

Now that the filter is defined, we can apply it on an entity class, for example on our Task:

@Entity
@Filter(name="user")
public class Task extends Model {
  ⋮
}

Finally, we must enable this particular filter. It is convenient to have it automatically applied on every request, so we can do that in an @Before interceptor:

@Before
public static void setFilters() {

    Long loggedInUser = 1L; // Replace this with the actual user id

    ((Session)JPA.em().getDelegate()).enableFilter("user").setParameter("user_id", loggedInUser);
}

Here we grab the instance of the Hibernate Session, enable the filter user that we defined, and set the parameter user_id. If you use Play’s secure module, you could get this user id from the session.

This interceptor can be defined on a superclass of your controllers, or on a class that you add to your controllers with an @With annotation.

Result

Now with the user filter applied, we can do a query like Task.findAll() and we only get the tasks of the user that is logged in. Also queries like Tasks.count() or Tasks.find("byDone", false) and even HQL queries only take tasks of the currently logged in user into account.

Every Play requests has a fresh Hibernate Session, so if you do not enable a filter during the request, you can just query your entire database. This can be useful for some global administration page. If you want to do a query without the filter after enabling it, you can disable it with Session)JPA.em().getDelegate(.disableFilter("user").

Additional uses

Filters can also be very useful for implementing a soft-delete. Add a filter that filters away records that are flagged as deleted, and you don’t have do modify any queries in your program. In general, a filter is useful everywhere you have multiple queries with a common selection criterium and you want to unclutter your queries.

Play Gotcha

Play 1.1.1 has a bug that bites you when you have a package-info.java file in a package that has a class with an enum or nested class. You get a "Compilation Error - The file /app/models/package-info.java could not be compiled. Error raised is : The type package-info is already defined" message. A patch is available and will hopefully make it into Play 1.2.

Meanwhile, you can apply the patch to your Play installation, or alternatively define the @FilterDef on the first-loaded model in your application.

Conclusion

For maintenance reasons, it might be preferable to keep all data of a multitenant application in a single database. To avoid unnecessary repetition of a query criterium that selects only data for the current user, you can use Hibernate filters. With the Play framework, it is very easy to enable such a filter in an interceptor.

expand_less