如何在创建Hibernate会话时拦截它们(Spring / Grails环境)

时间:2010-08-26 17:40:31

标签: hibernate spring grails aop

有没有办法在创建新的Hibernate会话时拦截它们?我需要访问每个Session实例以启用带参数的Hibernate过滤器。

我工作的唯一解决方案涉及包装SessionFactory,但这涉及很多半讨厌的黑客,并且它要求我实现大约60种方法,其中只有少数是有趣的。

Hibernate的SessionFactory实现是出于某种令人烦恼的原因而宣布最终的,因此扩展它不是一种选择。我也试过方面和Java代理没有任何运气。

5 个答案:

答案 0 :(得分:5)

我能够创建一个JDK代理:

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;
   }
}

您可以从自定义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));
   }
}

取决于使用代理而不是真实会话工厂的Spring的SpringSessionContext的修改版本:

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());
      }
   }
}

这需要在resources.groovy中注册以替换标准的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')]
   }
}

答案 1 :(得分:2)

我已经解决了这个问题(至少在Hibernate为这样的事情提供了适当的API之前)。解决方案的简短版本:

  1. 代理会话工厂
  2. 拦截getCurrentSession的方法调用并使用我们初始化的CurrentSessionContext实现(不是Hibernate)。
  3. 更长的版本: http://www.developer-b.com/blog/entry/1635/2010/oct/07/intercepting-hibernate-sessions

    来源/ Github: http://github.com/multi-tenant/grails-hibernate-hijacker(仍然非常实验性)

    感谢您的投入!

答案 2 :(得分:2)

Burt和Kimble的答案都有效,但你可以更容易地做到这一点。您需要创建一个实现Hibernate CurrentSessionContext类的类,但不需要为会话工厂创建代理,因为您可以覆盖会话上下文类中的会话创建行为,然后只需指定此类的名称在会话工厂bean的属性中。例如像这样编写会话上下文

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;
  }

}

然后在传递给会话工厂类的属性中,指定此类名:

hibernate.current_session_context_class=org.company.MySessionContext

例如,在典型的Spring场景中,您可以使用Spring工厂bean来实例化您的hibernate属性,如下所示:

<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>

然后通常您将使用Spring会话工厂bean创建会话工厂,例如(注意不同版本的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包含三个不同的会话上下文类,因此只需覆盖与您相关的一个:

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

这三个方法都有方法buildOrObtainSession,方法的javadoc实际上说&#34;提供用于子类化目的&#34;。如果您使用跨多个资源的事务(例如多个数据库或数据库和JMS队列),则需要JTA会话上下文,如果您只是在每个事务中访问单个资源,则ThreadLocalSessionContext就足够了。

答案 3 :(得分:1)

查看Hibernate-filter plugin - 这可能是您想要使用的内容,或者您​​至少可以看到该插件是如何实现的。

另外我相信Multi-tenant plugin可能有一些使用Hibernate Session过滤器的代码。

答案 4 :(得分:1)

在代码中只有一个地方你可以从hibernate请求一个新会话(例如在你的DAO的抽象基类中),并在那里启用你的过滤器,这可能是最干净的。