提交 5c27a042 编写于 作者: C Chris Beams

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
上级 88e2277b
......@@ -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
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册