From 5c27a042101b3ab5eaba12c440b194c88f53e685 Mon Sep 17 00:00:00 2001 From: Chris Beams Date: Tue, 26 Apr 2011 10:15:30 +0000 Subject: [PATCH] Automatically close SessionFactory objects SessionFactory objects created by SessionFactoryBuilderSupport#buildSessionFactory are now DisposableBean proxies that call SessionFactory#close and release any threadlocal DataSource object. This is the same behavior that has always occurred during LSFBean and ASFBean destruction lifecycles (and still does). This destruction logic has now been factored out into SessionFactoryBuilderSupport#closeHibernateSessionFactory such that all SFB types can reuse it easily. Note that LSFBean and ASFBean are subclasses, respectively, of SFBuilder and ASFBuilder and they each must disable the DisposableBean proxying in order to avoid duplicate attempts at closing the SessionFactory. See the implementations of wrapSessionFactoryIfNeccesary() for details. Issue: SPR-8114 --- ...rnateSessionFactoryConfigurationTests.java | 13 ++++ .../hibernate3/LocalSessionFactoryBean.java | 7 ++- .../SessionFactoryBeanDelegate.java | 25 ++------ .../SessionFactoryBeanOperations.java | 8 +++ .../SessionFactoryBuilderSupport.java | 63 ++++++++++++++++--- .../AnnotationSessionFactoryBean.java | 6 ++ 6 files changed, 94 insertions(+), 28 deletions(-) diff --git a/org.springframework.integration-tests/src/test/java/org/springframework/orm/hibernate3/HibernateSessionFactoryConfigurationTests.java b/org.springframework.integration-tests/src/test/java/org/springframework/orm/hibernate3/HibernateSessionFactoryConfigurationTests.java index 8d72df5d4f4..9444c3172bf 100644 --- a/org.springframework.integration-tests/src/test/java/org/springframework/orm/hibernate3/HibernateSessionFactoryConfigurationTests.java +++ b/org.springframework.integration-tests/src/test/java/org/springframework/orm/hibernate3/HibernateSessionFactoryConfigurationTests.java @@ -19,7 +19,9 @@ package org.springframework.orm.hibernate3; import static org.hamcrest.CoreMatchers.instanceOf; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.notNullValue; +import static org.hamcrest.Matchers.startsWith; import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; import java.io.File; import java.util.List; @@ -34,6 +36,7 @@ import org.hibernate.classic.Session; import org.junit.Ignore; import org.junit.Test; import org.springframework.beans.factory.BeanCreationException; +import org.springframework.beans.factory.DisposableBean; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -130,6 +133,16 @@ public class HibernateSessionFactoryConfigurationTests { } } + @Test + public void builtSessionFactoryIsDisposableBeanProxy() { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(AnnotationSessionFactoryConfig.class); + SessionFactory sessionFactory = ctx.getBean(SessionFactory.class); + assertThat(sessionFactory, instanceOf(DisposableBean.class)); + assertThat(sessionFactory.toString(), startsWith("DisposableBean proxy for SessionFactory")); + ctx.close(); + assertTrue("SessionFactory was not closed as expected", sessionFactory.isClosed()); + } + private void saveAndRetriveEntity(Class configClass) { SessionFactory sessionFactory = new AnnotationConfigApplicationContext(configClass).getBean(SessionFactory.class); diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/LocalSessionFactoryBean.java b/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/LocalSessionFactoryBean.java index 40786470aba..4bd2ac0dce9 100644 --- a/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/LocalSessionFactoryBean.java +++ b/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/LocalSessionFactoryBean.java @@ -17,8 +17,8 @@ package org.springframework.orm.hibernate3; import org.hibernate.HibernateException; - import org.hibernate.SessionFactory; + import org.springframework.dao.DataAccessException; import org.springframework.jdbc.support.SQLExceptionTranslator; @@ -110,6 +110,11 @@ public class LocalSessionFactoryBean extends SessionFactoryBuilder implements Se delegate.setPersistenceExceptionTranslator(hibernateExceptionTranslator); } + @Override + public SessionFactory wrapSessionFactoryIfNecessary(SessionFactory rawSf) { + return delegate.wrapSessionFactoryIfNecessary(rawSf); + } + /** * @deprecated as of Spring 3.1 in favor of {@link #newSessionFactory()} which * can access the internal {@code Configuration} instance via {@link #getConfiguration()}. diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/SessionFactoryBeanDelegate.java b/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/SessionFactoryBeanDelegate.java index 4bf3b613237..5ef2b179a32 100644 --- a/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/SessionFactoryBeanDelegate.java +++ b/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/SessionFactoryBeanDelegate.java @@ -16,8 +16,6 @@ package org.springframework.orm.hibernate3; -import javax.sql.DataSource; - import org.hibernate.HibernateException; import org.hibernate.SessionFactory; import org.hibernate.cache.RegionFactory; @@ -115,23 +113,7 @@ public class SessionFactoryBeanDelegate implements SessionFactoryBeanOperations } public void destroy() throws HibernateException { - builder.logger.info("Closing Hibernate SessionFactory"); - DataSource dataSource = builder.getDataSource(); - if (dataSource != null) { - // Make given DataSource available for potential SchemaExport, - // which unfortunately reinstantiates a ConnectionProvider. - SessionFactoryBuilderSupport.configTimeDataSourceHolder.set(dataSource); - } - try { - builder.beforeSessionFactoryDestruction(); - } - finally { - this.sessionFactory.close(); - if (dataSource != null) { - // Reset DataSource holder. - SessionFactoryBuilderSupport.configTimeDataSourceHolder.remove(); - } - } + SessionFactoryBuilderSupport.closeHibernateSessionFactory(this.builder, this.sessionFactory); } public void setJdbcExceptionTranslator(SQLExceptionTranslator jdbcExceptionTranslator) { @@ -142,6 +124,11 @@ public class SessionFactoryBeanDelegate implements SessionFactoryBeanOperations return hibernateExceptionTranslator.translateExceptionIfPossible(ex); } + public SessionFactory wrapSessionFactoryIfNecessary(SessionFactory rawSf) { + return rawSf; + } + + /** * @see SessionFactoryBuilderSupport#preBuildSessionFactory() */ diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/SessionFactoryBeanOperations.java b/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/SessionFactoryBeanOperations.java index e4c810d8aa1..ffe04266580 100644 --- a/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/SessionFactoryBeanOperations.java +++ b/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/SessionFactoryBeanOperations.java @@ -125,4 +125,12 @@ public interface SessionFactoryBeanOperations */ DataAccessException translateExceptionIfPossible(RuntimeException ex); + /** + * Override the default {@link DisposableBean} proxying behavior in + * {@link SessionFactoryBuilderSupport#wrapSessionFactoryIfNecessary(SessionFactory)} + * and return the raw {@code SessionFactory} instance, as {@link SessionFactory#close()} + * will be called during this FactoryBean's normal {@linkplain #destroy() destruction lifecycle}. + */ + SessionFactory wrapSessionFactoryIfNecessary(SessionFactory rawSf); + } diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/SessionFactoryBuilderSupport.java b/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/SessionFactoryBuilderSupport.java index a581581785f..a9a2295b123 100644 --- a/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/SessionFactoryBuilderSupport.java +++ b/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/SessionFactoryBuilderSupport.java @@ -18,7 +18,9 @@ package org.springframework.orm.hibernate3; import java.io.File; import java.lang.reflect.Array; +import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; +import java.lang.reflect.Proxy; import java.sql.Connection; import java.sql.SQLException; import java.sql.Statement; @@ -32,6 +34,7 @@ import javax.transaction.TransactionManager; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; + import org.hibernate.HibernateException; import org.hibernate.Interceptor; import org.hibernate.Session; @@ -45,7 +48,10 @@ import org.hibernate.engine.SessionFactoryImplementor; import org.hibernate.event.EventListeners; import org.hibernate.tool.hbm2ddl.DatabaseMetadata; import org.hibernate.transaction.JTATransactionFactory; + import org.springframework.beans.BeanUtils; +import org.springframework.beans.factory.DisposableBean; +import org.springframework.context.ConfigurableApplicationContext; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.Resource; import org.springframework.dao.DataAccessException; @@ -552,16 +558,37 @@ public abstract class SessionFactoryBuilderSupportThe default implementation simply returns the given SessionFactory as-is. - * Subclasses may override this to implement transaction awareness through - * a SessionFactory proxy, for example. - * @param rawSf the raw SessionFactory as built by {@link #buildSessionFactory()} - * @return the SessionFactory reference to expose + * Wrap the given {@code SessionFactory} with a proxy, if demanded. + *

The default implementation wraps the given {@code SessionFactory} as a Spring + * {@link DisposableBean} proxy in order to call {@link SessionFactory#close()} on + * {@code ApplicationContext} {@linkplain ConfigurableApplicationContext#close() shutdown}. + *

Subclasses may override this to implement transaction awareness through + * a {@code SessionFactory} proxy for example, or even to avoid creation of the + * {@code DisposableBean} proxy altogether. + * @param rawSf the raw {@code SessionFactory} as built by {@link #buildSessionFactory()} + * @return the {@code SessionFactory} reference to expose * @see #buildSessionFactory() */ - protected SessionFactory wrapSessionFactoryIfNecessary(SessionFactory rawSf) { - return rawSf; + protected SessionFactory wrapSessionFactoryIfNecessary(final SessionFactory rawSf) { + return (SessionFactory) Proxy.newProxyInstance( + this.beanClassLoader, + new Class[] { + SessionFactory.class, + DisposableBean.class + }, + new InvocationHandler() { + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + if (ReflectionUtils.isToStringMethod(method)) { + return String.format("DisposableBean proxy for SessionFactory [%s]", rawSf.toString()); + } + if (method.equals(DisposableBean.class.getMethod("destroy"))) { + closeHibernateSessionFactory(SessionFactoryBuilderSupport.this, rawSf); + rawSf.close(); + return null; + } + return method.invoke(rawSf, args); + } + }); } /** @@ -1409,4 +1436,24 @@ public abstract class SessionFactoryBuilderSupport builder, SessionFactory sessionFactory) { + builder.logger.info("Closing Hibernate SessionFactory"); + DataSource dataSource = builder.getDataSource(); + if (dataSource != null) { + // Make given DataSource available for potential SchemaExport, + // which unfortunately reinstantiates a ConnectionProvider. + SessionFactoryBuilderSupport.configTimeDataSourceHolder.set(dataSource); + } + try { + builder.beforeSessionFactoryDestruction(); + } + finally { + sessionFactory.close(); + if (dataSource != null) { + // Reset DataSource holder. + SessionFactoryBuilderSupport.configTimeDataSourceHolder.remove(); + } + } + } + } diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/annotation/AnnotationSessionFactoryBean.java b/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/annotation/AnnotationSessionFactoryBean.java index 3ecc5c9423a..68642082f1e 100644 --- a/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/annotation/AnnotationSessionFactoryBean.java +++ b/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/annotation/AnnotationSessionFactoryBean.java @@ -18,6 +18,7 @@ package org.springframework.orm.hibernate3.annotation; import org.hibernate.HibernateException; import org.hibernate.SessionFactory; + import org.springframework.context.ResourceLoaderAware; import org.springframework.core.io.ResourceLoader; import org.springframework.core.io.support.ResourcePatternUtils; @@ -134,6 +135,11 @@ public class AnnotationSessionFactoryBean extends AnnotationSessionFactoryBuilde delegate.setPersistenceExceptionTranslator(hibernateExceptionTranslator); } + @Override + public SessionFactory wrapSessionFactoryIfNecessary(SessionFactory rawSf) { + return delegate.wrapSessionFactoryIfNecessary(rawSf); + } + /** * @deprecated as of Spring 3.1 in favor of {@link #scanPackages()} which * can access the internal {@code AnnotationConfiguration} instance via