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

发布于 2024-09-16 03:43:12 字数 255 浏览 6 评论 0原文

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

我得到的唯一解决方案涉及包装 SessionFactory,但这涉及到许多半令人讨厌的黑客行为,并且需要我实现大约 60 个方法,其中只有少数是有趣的。

Hibernate 的 SessionFactory 实现由于某些烦人的原因被声明为最终的,因此扩展它不是一个选择。我也尝试过方面和 Java 代理,但没有任何运气。

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 not an option. I've also tried aspects and Java proxies without any luck.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

极度宠爱 2024-09-23 03:43:12

我能够创建一个 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')]
   }
}

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')]
   }
}
扭转时空 2024-09-23 03:43:12

我已经解决了这个问题(至少在 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 (仍然处于实验阶段)

感谢您的投入!

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!

方觉久 2024-09-23 03:43:12

伯特和金布尔的答案都有效,但您可以更轻松地做到这一点。您确实需要创建一个实现 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 实际上说“为子类化目的而提供”。如果您使用跨越多个资源(例如多个数据库或数据库和 JMS 队列)的事务,则需要 JTA 会话上下文,如果您仅在每个事务中访问单个资源,则 ThreadLocalSessionContext 就足够了。

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.

明天过后 2024-09-23 03:43:12

看一下 Hibernate-filter 插件 - 这可能就是您想要使用的或者你至少可以看看这个插件是如何做到的。

我还相信 多租户插件 可能有一些使用 Hibernate Session 过滤器的代码。

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.

星星的軌跡 2024-09-23 03:43:12

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

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文