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:
- Proxy the session factory
- 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.
精彩评论