How to enable hibernate filter for sessionFactory.getCurrentSession()?

sachink picture sachink · Apr 15, 2011 · Viewed 23.3k times · Source

Say there is a User table with structure:

User

  • List item
  • userId (PK)
  • company (PK)
  • userName
  • address ...etc

And I want to retrieve users only for the current company (company can be changed by the user through UI, so the company is a runtime parameter)

Similarly there are many other tables that have similar structure with common column (company), and I want to restrict data to only the current company, so I am using hibernate filter to filter out data.

Hibernate annotations:

<bean id="sessionFactory"
    class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">

    <property name="dataSource">
        <ref bean="dataSource" />
    </property>
    <property name="hibernateProperties">
        <props>
            <prop key="hibernate.dialect">Dialect....</prop>
            <prop key="hibernate.show_sql">true</prop>
            <prop key="hibernate.generate_statistics">true</prop>
            <prop key="hibernate.connection.release_mode">after_transaction</prop>
            <prop key="hibernate.cache.use_second_level_cache">false</prop>
        </props>
    </property>
    <property name="annotatedClasses">
        <list>
            <value>User</value>
        .....
        </list>
    </property>
</bean>

Filter definitions:

@org.hibernate.annotations.FilterDef(name="restrictToCurrentCompany",
    parameters = {@org.hibernate.annotations.ParamDef(
            name = "currentCompanyNumber", type = "int"
        )
    }
)
@Entity
@Table(name = "USER")
@org.hibernate.annotations.Filter(
        name = "restrictToCurrentCompany",
        condition="company = :currentCompanyNumber"
)
public class User implements Serializable {
    private int company;
    private String userName;
    ...etc..
}

Dao's:


@Repository
@Transactional(readOnly = true)
public class UserDAOImpl implements UserDAO {

    @Autowired(required = true)
    private SessionFactory sessionFactory;

    public Set getUsers(){
        .....Criteria queries to retrieve users for the current company     
    }

    private Session getSession(){
        return sessionFactory.getCurrentSession();
    }

}

If I change the getSession like so;

private Session getSession(){
    Session session = sessionFactory.getCurrentSession();
    Filter filter = session.enableFilter("restrictToCurrentCompany");
    filter.setParameter("currentCompanyNumber", UserUtils.getCurrentCompany());
    return sessionFactory.getCurrentSession();
}

then I can enable the filter and everything looks good, but instead of enabling the filter during getting session is there a simpler alternative to apply and enable filter for the whole session factory/application level? If so, how could I do so using spring configuration?

I tried hooking into hibernate interceptors (pre-load event listerns) but I am bit unsure if this is a correct approach or should I rather use the getSession method listed above to enable filters?

Answer

Chris DeLashmutt picture Chris DeLashmutt · Aug 21, 2011

The solution you have is pretty simple, but I'm guessing what you are trying for is to make it so that you don't have to provide a "getSession" implementation in each of your DAOs. Ultimately your method of implementing this is going to depend on how flexible you want to be with this filter. Here's two ways I might solve this.

The simplest way would be to simply make your UserDAOImpl extend a new base class that contains the "getSession" logic in it. This method would allow you to reduce code in that you would have this filter logic applied in most cases, but then you could override the filtering when you need to.

You could create something like this:

public class BaseDAO
{

    // ... possibly some other methods and variables

    @Autowired(required = true)
    private SessionFactory sessionFactory;

    protected Session getSession()
    {
        //Your session filter logic above
    }
}

Now you could just have your UserDAOImpl subclass this and get a session when it needs to do something. This is a very simple way to do what you are looking for, but it isn't foolproof. If you are writing a framework for others to use, then what would stop them from simply getting their own reference to your SessionFactory by having Spring inject it and then they could get an unfiltered Session? You might want this in certain circumstances for administrative processes that can act on all data, but the next way I'll describe should prevent this from happening.

This second way to solve the problem involves using AOP to wrap the getSession method of SessionFactory with your logic to apply the filter before the session is returned. This method means that even if someone gets a reference to your SessionFactory themselves, they will still have this filtering logic applied.

First, if you aren't familiar with AOP in spring have a look at the reference http://static.springsource.org/spring/docs/current/spring-framework-reference/html/aop.html. I'm going to use the schema based method to apply advice to Hibernate, because we don't want to modify the source of Hibernate. ;) You can find the specifics of this method at http://static.springsource.org/spring/docs/current/spring-framework-reference/html/aop.html#aop-schema.

First, make sure you have the following schema and aop:config section in your application context XML for spring:

<?xml version="1.0" encoding="UTF-8"?>
<beans ...
    xmlns:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="
        ...
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">

    ...

<aop:config>
    <aop:aspect id="forceFilter" ref="sessionFilterAdvice">
        <aop:pointcut id="hibernateSessionFactoryGetSession"
            expression="execution(* org.hibernate.SessionFactory.openSession(..))" />
        <aop:after-returning method="setupFilter"
            pointcut-ref="hibernateSessionFactoryGetSession" returning="session" />
    </aop:aspect>
</aop:config>

    ...
</beans>

Next, you need to add a bean to your project to implement the sessionFilterAdvice bean we reference above with the aop:aspect tag. Create the following class:

package net.grogscave.example;

import org.hibernate.Filter;
import org.hibernate.Session;
import org.springframework.stereotype.Service;

@Service
public class SessionFilterAdvice
{
    public void setupFilter(Session session)
    {
        Session session = sessionFactory.getCurrentSession();
        Filter filter = session.enableFilter("restrictToCurrentCompany");
        filter.setParameter("currentCompanyNumber", UserUtils.getCurrentCompany());
    }
}

The final thing to make sure of is that your project includes the spring-aop jar and the aspectjweaver jar. I don't know if you use dependency management or not, but you somehow need to get those jars into your project classpath.

You should now be able to recompile your project, and now any calls to any of the openSession methods on classes that implement SessionFactory will add your filter to them.