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