开发者

How to intercept all Hibernate sessions when they're created (Spring / Grails environment)

Is there a way of intercepting all new Hibernate sessions when they're created? I need to access each Session instance to enable a Hibernate filter with a parameter.

The only solution I've gotten working has involved wrapping the SessionFactory, but this involved a lot of semi nasty hacks as well as it required me to implement around 60 methods, where only a few are interesting.

Hibernate's SessionFactory implementation is for some annoying reason declared final so extending it is开发者_StackOverflow中文版 not an option. I've also tried aspects and Java proxies without any luck.


I was able to create a JDK proxy:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Date;

import org.hibernate.SessionFactory;
import org.hibernate.engine.SessionFactoryImplementor;

public class SessionFactoryProxyCreator {

   public static SessionFactory instance;

   public static SessionFactory createProxy(final SessionFactory realSessionFactory) {
      ClassLoader cl = SessionFactory.class.getClassLoader();
      Class<?>[] interfaces = new Class[] { SessionFactory.class, SessionFactoryImplementor.class };
      instance = (SessionFactory)Proxy.newProxyInstance(cl, interfaces, new InvocationHandler() {
         public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

            if ("openSession".equals(method.getName())) {
               System.out.println("NEW SESSION AT " + new Date());
            }

            return method.invoke(realSessionFactory, args);
         }
      });

      return instance;
   }
}

and you would call this from a custom SessionFactoryBean:

import org.codehaus.groovy.grails.orm.hibernate.ConfigurableLocalSessionFactoryBean;
import org.hibernate.HibernateException;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;

public class MyConfigurableLocalSessionFactoryBean extends ConfigurableLocalSessionFactoryBean {

   public MyConfigurableLocalSessionFactoryBean() {
      setCurrentSessionContextClass(MyCurrentSessionContext.class);
   }

   @Override
   protected SessionFactory buildSessionFactory() throws Exception {
      setExposeTransactionAwareSessionFactory(false);
      return SessionFactoryProxyCreator.createProxy(super.buildSessionFactory());
   }

   @Override
   protected SessionFactory newSessionFactory(Configuration config) throws HibernateException {
      setExposeTransactionAwareSessionFactory(false);
      return SessionFactoryProxyCreator.createProxy(super.newSessionFactory(config));
   }
}

which depends on a modified version of Spring's SpringSessionContext that uses the proxy instead of the real session factory:

import org.hibernate.HibernateException;
import org.hibernate.classic.Session;
import org.hibernate.context.CurrentSessionContext;
import org.hibernate.engine.SessionFactoryImplementor;
import org.springframework.orm.hibernate3.SessionFactoryUtils;

public class MyCurrentSessionContext implements CurrentSessionContext {

   public MyCurrentSessionContext(SessionFactoryImplementor sessionFactory) {
      // ignore the real sessionFactory, need to use the proxy
   }

   public Session currentSession() throws HibernateException {
      try {
         return (org.hibernate.classic.Session)SessionFactoryUtils.doGetSession(
               SessionFactoryProxyCreator.instance, false);
      }
      catch (IllegalStateException e) {
         throw new HibernateException(e.getMessage());
      }
   }
}

This needs to be registered in resources.groovy to replace the standard Grails ConfigurableLocalSessionFactoryBean:

import org.codehaus.groovy.grails.commons.ApplicationHolder as AH
import org.codehaus.groovy.grails.orm.hibernate.events.PatchedDefaultFlushEventListener

beans = {

   sessionFactory(MyConfigurableLocalSessionFactoryBean) {

      def ds = AH.application.config.dataSource
      def hibConfig = AH.application.config.hibernate

      dataSource = ref('dataSource')
      List hibConfigLocations = []
      if (AH.application.classLoader.getResource('hibernate.cfg.xml')) {
         hibConfigLocations << 'classpath:hibernate.cfg.xml'
      }
      def explicitLocations = hibConfig?.config?.location
      if (explicitLocations) {
         if (explicitLocations instanceof Collection) {
            hibConfigLocations.addAll(explicitLocations.collect { it.toString() })
         }
         else {
            hibConfigLocations << hibConfig.config.location.toString()
         }
      }
      configLocations = hibConfigLocations
      if (ds?.configClass) {
         configClass = ds.configClass
      }
      hibernateProperties = ref('hibernateProperties')
      grailsApplication = ref('grailsApplication', true)
      lobHandler = ref('lobHandlerDetector')
      entityInterceptor = ref('entityInterceptor')
      eventListeners = ['flush': new PatchedDefaultFlushEventListener(),
                        'pre-load':    ref('eventTriggeringInterceptor'),
                        'post-load':   ref('eventTriggeringInterceptor'),
                        'save':        ref('eventTriggeringInterceptor'),
                        'save-update': ref('eventTriggeringInterceptor'),
                        'post-insert': ref('eventTriggeringInterceptor'),
                        'pre-update':  ref('eventTriggeringInterceptor'),
                        'post-update': ref('eventTriggeringInterceptor'),
                        'pre-delete':  ref('eventTriggeringInterceptor'),
                        'post-delete': ref('eventTriggeringInterceptor')]
   }
}


I've solved this problem (at least until Hibernate provides a proper API for things like this). Short version of the solution:

  1. Proxy the session factory
  2. Intercept method invocations to getCurrentSession and use a CurrentSessionContext implementation we've initialized (not Hibernate).

Longer version: http://www.developer-b.com/blog/entry/1635/2010/oct/07/intercepting-hibernate-sessions

Sources / Github: http://github.com/multi-tenant/grails-hibernate-hijacker (still very experimental)

Thanks for the input!


Both Burt and Kimble's answers will work, but you can do this more easily. You do need to create a class that implements the Hibernate CurrentSessionContext class, but there is no need to create a proxy for the session factory, as you can override the session creation behaviour in the session context class, then simply specify the name of this class in the properties to your session factory bean. e.g. write your session context like this

import org.hibernate.FlushMode;
import org.hibernate.classic.Session;
import org.hibernate.context.JTASessionContext;
import org.hibernate.engine.SessionFactoryImplementor;

public class MySessionContext extends JTASessionContext {

  public MySessionContext(SessionFactoryImplementor factory) {
    super(factory);
  }

  @Override
  protected Session buildOrObtainSession() {
    Session session = super.buildOrObtainSession();
    // do stuff to the session here
    return session;
  }

}

Then in the properties you pass to your session factory class, specify this class name:

hibernate.current_session_context_class=org.company.MySessionContext

For example, in a typical Spring scenario, you might use a Spring factory bean to instantiate your hibernate properties, like this:

<bean id="hibernateProperties" class="org.springframework.beans.factory.config.PropertiesFactoryBean">
    <property name="properties">
        <props>
            <prop key="hibernate.current_session_context_class">org.company.MySessionContext</prop> 
            // your other Hibernate properties here     
        </props>
    </property>
</bean>

Then typically you'll create a session factory using a Spring session factory bean, such as (note package name will differ for different versions of Hibernate):

<bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
    <property name="dataSource" ref="dataSource"/>
    <property name="configLocations" ref="hibernateConfigLocations"/>
    <property name="hibernateProperties" ref="hibernateProperties"/>
    <property name="entityInterceptor" ref="hibernateEntityInterceptor"/>
    <property name="jtaTransactionManager" ref="transactionManager"/>
    <property name="implicitNamingStrategy" ref="underscoresNamingStrategy"/>
</bean> 

Hibernate includes three different session context classes, so just override the one relevant to you:

org.hibernate.context.JTASessionContext
org.hibernate.context.ThreadLocalSessionContext
org.hibernate.context.ManagedSessionContext

All three have the method buildOrObtainSession, and the javadoc for the method actually says "provided for subclassing purposes". A JTA session context will be required if you are using transactions that span multiple resources, such as multiple databases, or databases and JMS queues, if you are just accessing a single resource in each transaction, a ThreadLocalSessionContext would be sufficient.


Take a Look at the Hibernate-filter plugin - this may be what you want to use or you can at least see how that plugin does it.

Also I believe that the Multi-tenant plugin may have some code that uses Hibernate Session filters.


It would probably cleanest to have only one place in code where you request a new session from hibernate (for instance in an abstract base class of your DAOs), and enable your filter there.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜