Как перехватить все сессии Hibernate при их создании (среда Spring / Grails) - PullRequest
4 голосов
/ 26 августа 2010

Есть ли способ перехвата всех новых сессий Hibernate при их создании?Мне нужно получить доступ к каждому экземпляру сеанса, чтобы включить фильтр Hibernate с параметром.

Единственное решение, которое я получил, заключалось в том, чтобы обернуть SessionFactory, но для этого потребовалось много полусонадных хаков, а также мне потребовалось реализовать около 60 методов, из которых только несколько интересны.

Реализация SiberFactory в Hibernate по какой-то назойливой причине объявлена ​​окончательной, поэтому расширение ее невозможно.Я также попробовал аспекты и Java прокси без какой-либо удачи.

Ответы [ 5 ]

5 голосов
/ 29 августа 2010

Мне удалось создать 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));
   }
}

, который зависит от модифицированной версии SpringSessionContext Spring, которыйиспользует прокси вместо реальной фабрики сеансов:

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')]
   }
}
2 голосов
/ 13 января 2016

Ответы Берта и Кимбла сработают, но вы можете сделать это проще.Вам необходимо создать класс, который реализует класс Hibernate CurrentSessionContext, но нет необходимости создавать прокси для фабрики сеансов, поскольку вы можете переопределить поведение создания сеанса в классе контекста сеанса, а затем просто указать имя этого класса.в свойствах вашего сессионного фабричного компонента.например, напишите свой контекст сеанса следующим образом:

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, вы можете использовать фабричный bean-компонент Spring для создания экземпляров своих свойств гибернации, например:

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

Затем обычно вы создаете фабрику сессий с использованием bean-компонента фабрики Spring, например (имя пакета заметок будет отличатьсядля разных версий 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 для метода на самом деле говорит «предоставлен для целей подклассов».Контекст сеанса JTA потребуется, если вы используете транзакции, которые охватывают несколько ресурсов, таких как несколько баз данных, или базы данных и очереди JMS, если вы просто обращаетесь к одному ресурсу в каждой транзакции, то ThreadLocalSessionContext будет достаточно.

2 голосов
/ 09 октября 2010

Я решил эту проблему (по крайней мере до тех пор, пока Hibernate не предоставит надлежащий API для подобных вещей). Краткая версия решения:

  1. Прокси фабрики сессий
  2. Перехватывает вызовы метода для getCurrentSession и использует инициализацию реализации CurrentSessionContext, которую мы инициализировали (не Hibernate)

Более длинная версия: http://www.developer -b.com / блог / запись / 1635/2010 / октябрь / 07 / перехватывать-зимуют-сессий

Источники / Github: http://github.com/multi-tenant/grails-hibernate-hijacker (все еще очень экспериментально)

Спасибо за ввод!

1 голос
/ 29 августа 2010

Вероятно, было бы лучше иметь только одно место в коде, где вы запрашиваете новый сеанс из hibernate (например, в абстрактном базовом классе ваших DAO) и включаете там свой фильтр.

1 голос
/ 29 августа 2010

Взгляните на плагин Hibernate-filter - это может быть то, что вы хотите использовать, или вы можете хотя бы посмотреть, как этот плагин делает это.

Также я считаю, что Мультитенантный плагин может иметь некоторый код, который использует фильтры Hibernate Session.

...