代码之家  ›  专栏  ›  技术社区  ›  Kimble

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

  •  4
  • Kimble  · 技术社区  · 15 年前

    我工作过的唯一解决方案是包装SessionFactory,但这涉及到许多半恶劣的黑客行为,而且它需要我实现大约60个方法,其中只有少数方法是有趣的。

    5 回复  |  直到 15 年前
        1
  •  5
  •   Burt Beckwith    15 年前

    我可以创建一个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));
       }
    }
    

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

    需要在中注册资源.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')]
       }
    }
    
        2
  •  2
  •   Vlastimil Ovčáčík    9 年前

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

    1. 代理会话工厂
    2. 拦截对getCurrentSession的方法调用,并使用我们初始化的CurrentSessionContext实现(不是Hibernate)。

    较长版本: http://www.developer-b.com/blog/entry/1635/2010/oct/07/intercepting-hibernate-sessions

    来源/Github: http://github.com/multi-tenant/grails-hibernate-hijacker

        3
  •  2
  •   Hedley    8 年前

    伯特和金布尔的答案都会奏效,但你可以更容易地做到这一点。您确实需要创建一个实现Hibernate CurrentSessionContext类的类,但是不需要为会话工厂创建代理,因为您可以重写会话上下文类中的会话创建行为,然后只需在会话工厂bean的属性中指定该类的名称。e、 g.像这样编写会话上下文

    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实际上表示“为子类化目的而提供”。如果您正在使用跨多个资源的事务(如多个数据库或数据库和JMS队列),则需要JTA会话上下文;如果您只是在每个事务中访问单个资源,则ThreadLocalSessionContext就足够了。

        4
  •  1
  •   Colin Harrington    15 年前
        5
  •  1
  •   meriton    15 年前

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

    推荐文章