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
This commit is contained in:
parent
88e2277bff
commit
5c27a04210
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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()}.
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 SessionFactoryBuilderSupport<This extends SessionFactoryBu
|
|||
}
|
||||
|
||||
/**
|
||||
* Wrap the given SessionFactory with a proxy, if demanded.
|
||||
* <p>The 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.
|
||||
* <p>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}.
|
||||
* <p>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<This extends SessionFactoryBu
|
|||
return configTimeLobHandlerHolder.get();
|
||||
}
|
||||
|
||||
static void closeHibernateSessionFactory(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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue