Help

Hibernate3 adds the ability to pre-define filter criteria and attach those filters at both a class and a collection level. What's a pre-defined filter criteria? Well, it's the ability to define a limit clause very similiar to the existing where attribute available on the class and various collection elements. Except these filter conditions can be parameterized! The application can then make the decision at runtime whether given filters should be enabled and what their parameter values should be.

Configuration

In order to use filters, they must first be defined and then attached to the appropriate mapping elements. To define a filter, use the new <filter-def/> element within a <hibernate-mapping/> element:

<filter-def name="myFilter">
    <filter-param name="myFilterParam" type="string"/>
</filter-def>

Then, this filter can be attched to a class:

<class name="myClass" ...>
    ...
    <filter name="myFilter" condition=":myFilterParam = my_filtered_column"/>
</class>

or, to a collection:

<set ...>
    <filter name="myFilter" condition=":myFilterParam = my_filtered_column"/>
</set>

or, even to both (or multiples of each) at the same time!

Usage

In support of this, a new interface was added to Hibernate3, org.hibernate.Filter, and some new methods added to org.hibernate.Session. The new methods on Session are: enableFilter(String filterName), getEnabledFilter(String filterName), and disableFilter(String filterName). By default, filters are not enabled for a given session; they must be explcitly enabled through use of the Session.enabledFilter() method, which returns an instance of the new Filter interface. Using the simple filter defined above, this would look something like:

session.enableFilter("myFilter").setParameter("myFilterParam", "some-value");

Note that methods on the org.hibernate.Filter interface do allow the method-chaining common to much of Hibernate.

Big Deal

This is all functionality that was available in Hibernate before version 3, right? Of course. But before version 3, this was all manual processes by application code. To filter a collection you'd need to load the entity containing the collection and then apply the collection to the Session.filter() method. And for entity filtration you'd have to write stuff that manually modified the HQL string by hand or a custom Interceptor.

This new feature provides a clean and consistent way to apply these types of constraints. The Hibernate team envisions the usefulness of this feature in everything from internationalization to temporal data to security considerations (and even combinations of these at the same time) and much more. Of course it's hard to envision the potential power of this feature given the simple example used so far, so let's look at some slightly more in depth usages.

Temporal Data Example

Say you have an entity that follows the effective record database pattern. This entity has multiple rows each varying based on the date range during which that record was effective (possibly even maintained via a Hibernate Interceptor). An employment record might be a good example of such data, since employees might come and go and come back again. Further, say you are developing a UI which always needs to deal in current records of employment data. To use the new filter feature to acheive these goals, we would first need to define the filter and then attach it to our Employee class:

<filter-def name="effectiveDate">
    <filter-param name="asOfDate" type="date"/>
</filter-def>

<class name="Employee" ...>
    ...
    <many-to-one name="department" column="dept_id" class="Department"/>
    <property name="effectiveStartDate" type="date" column="eff_start_dt"/>
    <property name="effectiveEndDate" type="date" column="eff_end_dt"/>
    ...
    <!--
        Note that this assumes non-terminal records have an eff_end_dt set to a max db date
        for simplicity-sake
    -->
    <filter name="effectiveDate" condition=":asOfDate BETWEEN eff_start_dt and eff_end_dt"/>
</class>

<class name="Department" ...>
    ...
    <set name="employees" lazy="true">
        <key column="dept_id"/>
        <one-to-many class="Employee"/>
        <filter name="effectiveDate" condition=":asOfDate BETWEEN eff_start_dt and eff_end_dt"/>
    </set>
</class>

Then, in order to ensure that you always get back currently effective records, simply enable the filter on the session prior to retrieving employee data:

Session session = ...;
session.enabledFilter("effectiveDate").setParameter("asOfDate", new Date());
List results = session.createQuery("from Employee as e where e.salary > :targetSalary")
        .setLong("targetSalary", new Long(1000000))
        .list();

In the HQL above, even though we only explicitly mentioned a salary constraint on the results, because of the enabled filter the query will return only currently active employees who have a salary greater than a million dollars (lucky stiffs).

Even further, if a given department is loaded from a session with the effectiveDate filter enabled, its employee collection will only contain active employees.

Security Example

Imagine we have an application that assigns each user an access level, and that some sensitive entities in the system are assigned access levels (way simplistic, I understand, but this is just illustration). So a user should be able to see anything where their assigned access level is greater than that assigned to the entity they are trying to see. Again, first we need to define the filter and apply it:

<filter-def name="accessLevel">
    <filter-param name="userLevel" type="int"/>
</filter-def>

<class name="Opportunity" ...>
    ...
    <many-to-one name="region" column="region_id" class="Region"/>
    <property name="amount" type="Money">
        <column name="amt"/>
        <cloumn name="currency"/>
    </property>
    <property name="accessLevel" type="int" column="access_lvl"/>
    ...
    <filter name="accessLevel"><![CDATA[:userLevel >= access_lvl]]></filter>
</class>

<class name="Region" ...>
    ...
    <set name="opportunities" lazy="true">
        <key column="region_id"/>
        <one-to-many class="Opportunity"/>
        <filter name="accessLevel"><![CDATA[:userLevel >= access_lvl]]></filter>
    </set>
    ...
</class>

Next, our application code would need to enable the filter:

User user = ...;
Session session = ...;
session.enableFilter("accessLevel").setParameter("userLevel", user.getAccessLevel());

At this point, loading a Region would filter its opportunities collection based on the current user's access level:

Region region = (Region) session.get(Region.class, "EMEA");
region.getOpportunities().size(); // <- limited to those accessible by the user's level

Conclusion

These were some pretty simple examples. But hopefully, they'll give you a glimpse of how powerful these filters can be and maybe sparked some thoughts as to where you might be able to apply such constraints within your own application. This can become even more powerful in combination with various interception methods, like web filters, etc. Also a note: if you plan on using filters with outer joining (either through HQL or load fetching) be careful of the direction of the condition expression. Its safest to set this up for left outer joining; in general, place the parameter first followed by the column name(s) after the operator.

10 comments:
 
13. Aug 2004, 10:11 CET | Link
Daniel Miller
Filters! I was just thinking Hibernate needed something like this. However, I really feel under whelmed with what was described.

My idea was almost exactly what was described in this article with one exception: instead of making this filter feature only available in a DAO, it should be available on the data object itself. For example, have something like a getter with a method signature like this:

public Set getEmployees(Date asOfDate)

I'm using the temporal data example. This method would be found on the Employee object--it is just an overload of the normal getEmployees() method with no params. What I am getting at here is I don't want to write custom find queries for all of my queries. I should just be able to get the data from the object itself instead of having to run to the DAO each time I want more than just a simple getSomeProperty().

I'm not trashing your idea here, just suggesting how I, as a Hibernate user, would like to see it improved.
ReplyQuote
 
14. Aug 2004, 01:33 CET | Link
steve
mystified as to how this is an "improved" approach. first, now your domain entity is not following java beans convention (getters *do not* have parameters). second your introducing persistence-related concerns into your domain model.

I am assuming you are talking about Hibernate somehow doing this for you. Is hibernate just supposed to become aware that you now have this additional method which happens to have the same name as a property getter, but with an additional parameter meant to accept a possibly set filter condition? What if your Department.employees collection happens to have two or more filters attached to it? What if the collections already been initialized?
 
14. Aug 2004, 04:47 CET | Link
Excellent.

Is it possible to use any expression in the filter ?

For example, something like
:userlevel > parent.accesslevel or parent.owner = :owner
 
14. Aug 2004, 05:59 CET | Link
Christian
Yes, it can be an arbitrary SQL expression.
 
14. Aug 2004, 06:35 CET | Link
steve
To echo christian, it has to be SQL (tables and columns).

Also, you need to be careful about the order of your expression if they will be used in outer joining. Generally, this means the parameter should come before the operator.
 
14. Aug 2004, 12:40 CET | Link
Daniel Miller
> first ... getters *do not* have parameters

Good point--sorry I was brainstorming. Maybe it could use some other prefix: findEmployees(Date asOfDate)

> second your introducing persistence-related concerns into your domain model.

Could you elaborate on how passing a parameter into a method to improve the result is "introducing persistence-related concerns"?

>Is hibernate just supposed to become aware that you now have this additional method which happens to have the same name as a property getter, but with an additional parameter meant to accept a possibly set filter condition?

Why not? It already does this with my get/set property names. I realize that this may not be the most elegant solution. If you have a better one I'm all ears. Maybe it would consist of another markup construct in the mapping file to specify which methods Hibernate should look for.

> What if your Department.employees collection happens to have two or more filters attached to it?

It would be cool if it would accept different combinations of methods:

findEmployees(Date asOfDate)
findEmployees(String someOtherParam)
findEmployees(Date asOfDate, String someOtherParam)
etc.

> What if the collections already been initialized?

Why couldn't it maintain two collections that happen to have overlapping elements? I do this now (Hibernate2) by creating two 's in my mapping file:

Normal one-to-many association:
Set getEmployees()

"Filtered" one-to-many association:
Set getEnabledEmployees()

The second set has a where-clause on it that returns employees with a status of enabled. This is nice for simple situations, but I want to pass a parameter in for more complex situations.

I have this scenario a lot where I need a subset of the data associated with a domain object and I want to get that subset from the domain object, not a DAO. Am I doing something wrong here or do I just have to suck it up?
 
18. Aug 2004, 09:21 CET | Link
Sanjiv Jivan | sjivan(AT)yahoo.com
I posted a response on theserverside Hibernate3 filters thread but got no response.

Does Hibernate plan on supporting the notion of sorting in the way it supports filtering by specifying filter-def.

In our Web UI, we have several data driven tables that must support multi-column sorting and multi-column filtering in addition to paging.

The filter-def functionality would help support data filtering in a clean way. A similar thing for sorting would be useful.

Comments/Thoughts?

Thanks,
Sanjiv
 
20. Jun 2009, 15:11 CET | Link

My note/blo on Hibernate Filter example, please comment on it to improve my writing skill.

Example on Hibernate Filter
17. Jul 2010, 17:29 CET | Link

incase you wanna have idea about internet marketing just click on the site. its related top this site

 
02. Aug 2010, 16:44 CET | Link

Could you elaborate on how passing a parameter into a method to improve the result is introducing persistence-related concerns?

Post Comment