Skip to content

Commit 5c27a04

Browse files
committed
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
1 parent 88e2277 commit 5c27a04

File tree

6 files changed

+94
-28
lines changed

6 files changed

+94
-28
lines changed

org.springframework.integration-tests/src/test/java/org/springframework/orm/hibernate3/HibernateSessionFactoryConfigurationTests.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@
1919
import static org.hamcrest.CoreMatchers.instanceOf;
2020
import static org.hamcrest.CoreMatchers.is;
2121
import static org.hamcrest.CoreMatchers.notNullValue;
22+
import static org.hamcrest.Matchers.startsWith;
2223
import static org.junit.Assert.assertThat;
24+
import static org.junit.Assert.assertTrue;
2325

2426
import java.io.File;
2527
import java.util.List;
@@ -34,6 +36,7 @@
3436
import org.junit.Ignore;
3537
import org.junit.Test;
3638
import org.springframework.beans.factory.BeanCreationException;
39+
import org.springframework.beans.factory.DisposableBean;
3740
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
3841
import org.springframework.context.annotation.Bean;
3942
import org.springframework.context.annotation.Configuration;
@@ -130,6 +133,16 @@ public void usingSessionFactoryBuilder_withLateCustomConfigurationClass() throws
130133
}
131134
}
132135

136+
@Test
137+
public void builtSessionFactoryIsDisposableBeanProxy() {
138+
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(AnnotationSessionFactoryConfig.class);
139+
SessionFactory sessionFactory = ctx.getBean(SessionFactory.class);
140+
assertThat(sessionFactory, instanceOf(DisposableBean.class));
141+
assertThat(sessionFactory.toString(), startsWith("DisposableBean proxy for SessionFactory"));
142+
ctx.close();
143+
assertTrue("SessionFactory was not closed as expected", sessionFactory.isClosed());
144+
}
145+
133146

134147
private void saveAndRetriveEntity(Class<?> configClass) {
135148
SessionFactory sessionFactory = new AnnotationConfigApplicationContext(configClass).getBean(SessionFactory.class);

org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/LocalSessionFactoryBean.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@
1717
package org.springframework.orm.hibernate3;
1818

1919
import org.hibernate.HibernateException;
20-
2120
import org.hibernate.SessionFactory;
21+
2222
import org.springframework.dao.DataAccessException;
2323
import org.springframework.jdbc.support.SQLExceptionTranslator;
2424

@@ -110,6 +110,11 @@ public void setPersistenceExceptionTranslator(
110110
delegate.setPersistenceExceptionTranslator(hibernateExceptionTranslator);
111111
}
112112

113+
@Override
114+
public SessionFactory wrapSessionFactoryIfNecessary(SessionFactory rawSf) {
115+
return delegate.wrapSessionFactoryIfNecessary(rawSf);
116+
}
117+
113118
/**
114119
* @deprecated as of Spring 3.1 in favor of {@link #newSessionFactory()} which
115120
* can access the internal {@code Configuration} instance via {@link #getConfiguration()}.

org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/SessionFactoryBeanDelegate.java

Lines changed: 6 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,6 @@
1616

1717
package org.springframework.orm.hibernate3;
1818

19-
import javax.sql.DataSource;
20-
2119
import org.hibernate.HibernateException;
2220
import org.hibernate.SessionFactory;
2321
import org.hibernate.cache.RegionFactory;
@@ -115,23 +113,7 @@ public void afterPropertiesSet() throws Exception {
115113
}
116114

117115
public void destroy() throws HibernateException {
118-
builder.logger.info("Closing Hibernate SessionFactory");
119-
DataSource dataSource = builder.getDataSource();
120-
if (dataSource != null) {
121-
// Make given DataSource available for potential SchemaExport,
122-
// which unfortunately reinstantiates a ConnectionProvider.
123-
SessionFactoryBuilderSupport.configTimeDataSourceHolder.set(dataSource);
124-
}
125-
try {
126-
builder.beforeSessionFactoryDestruction();
127-
}
128-
finally {
129-
this.sessionFactory.close();
130-
if (dataSource != null) {
131-
// Reset DataSource holder.
132-
SessionFactoryBuilderSupport.configTimeDataSourceHolder.remove();
133-
}
134-
}
116+
SessionFactoryBuilderSupport.closeHibernateSessionFactory(this.builder, this.sessionFactory);
135117
}
136118

137119
public void setJdbcExceptionTranslator(SQLExceptionTranslator jdbcExceptionTranslator) {
@@ -142,6 +124,11 @@ public DataAccessException translateExceptionIfPossible(RuntimeException ex) {
142124
return hibernateExceptionTranslator.translateExceptionIfPossible(ex);
143125
}
144126

127+
public SessionFactory wrapSessionFactoryIfNecessary(SessionFactory rawSf) {
128+
return rawSf;
129+
}
130+
131+
145132
/**
146133
* @see SessionFactoryBuilderSupport#preBuildSessionFactory()
147134
*/

org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/SessionFactoryBeanOperations.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,4 +125,12 @@ public interface SessionFactoryBeanOperations
125125
*/
126126
DataAccessException translateExceptionIfPossible(RuntimeException ex);
127127

128+
/**
129+
* Override the default {@link DisposableBean} proxying behavior in
130+
* {@link SessionFactoryBuilderSupport#wrapSessionFactoryIfNecessary(SessionFactory)}
131+
* and return the raw {@code SessionFactory} instance, as {@link SessionFactory#close()}
132+
* will be called during this FactoryBean's normal {@linkplain #destroy() destruction lifecycle}.
133+
*/
134+
SessionFactory wrapSessionFactoryIfNecessary(SessionFactory rawSf);
135+
128136
}

org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/SessionFactoryBuilderSupport.java

Lines changed: 55 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@
1818

1919
import java.io.File;
2020
import java.lang.reflect.Array;
21+
import java.lang.reflect.InvocationHandler;
2122
import java.lang.reflect.Method;
23+
import java.lang.reflect.Proxy;
2224
import java.sql.Connection;
2325
import java.sql.SQLException;
2426
import java.sql.Statement;
@@ -32,6 +34,7 @@
3234

3335
import org.apache.commons.logging.Log;
3436
import org.apache.commons.logging.LogFactory;
37+
3538
import org.hibernate.HibernateException;
3639
import org.hibernate.Interceptor;
3740
import org.hibernate.Session;
@@ -45,7 +48,10 @@
4548
import org.hibernate.event.EventListeners;
4649
import org.hibernate.tool.hbm2ddl.DatabaseMetadata;
4750
import org.hibernate.transaction.JTATransactionFactory;
51+
4852
import org.springframework.beans.BeanUtils;
53+
import org.springframework.beans.factory.DisposableBean;
54+
import org.springframework.context.ConfigurableApplicationContext;
4955
import org.springframework.core.io.ClassPathResource;
5056
import org.springframework.core.io.Resource;
5157
import org.springframework.dao.DataAccessException;
@@ -552,16 +558,37 @@ protected void postBuildSessionFactory() {
552558
}
553559

554560
/**
555-
* Wrap the given SessionFactory with a proxy, if demanded.
556-
* <p>The default implementation simply returns the given SessionFactory as-is.
557-
* Subclasses may override this to implement transaction awareness through
558-
* a SessionFactory proxy, for example.
559-
* @param rawSf the raw SessionFactory as built by {@link #buildSessionFactory()}
560-
* @return the SessionFactory reference to expose
561+
* Wrap the given {@code SessionFactory} with a proxy, if demanded.
562+
* <p>The default implementation wraps the given {@code SessionFactory} as a Spring
563+
* {@link DisposableBean} proxy in order to call {@link SessionFactory#close()} on
564+
* {@code ApplicationContext} {@linkplain ConfigurableApplicationContext#close() shutdown}.
565+
* <p>Subclasses may override this to implement transaction awareness through
566+
* a {@code SessionFactory} proxy for example, or even to avoid creation of the
567+
* {@code DisposableBean} proxy altogether.
568+
* @param rawSf the raw {@code SessionFactory} as built by {@link #buildSessionFactory()}
569+
* @return the {@code SessionFactory} reference to expose
561570
* @see #buildSessionFactory()
562571
*/
563-
protected SessionFactory wrapSessionFactoryIfNecessary(SessionFactory rawSf) {
564-
return rawSf;
572+
protected SessionFactory wrapSessionFactoryIfNecessary(final SessionFactory rawSf) {
573+
return (SessionFactory) Proxy.newProxyInstance(
574+
this.beanClassLoader,
575+
new Class<?>[] {
576+
SessionFactory.class,
577+
DisposableBean.class
578+
},
579+
new InvocationHandler() {
580+
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
581+
if (ReflectionUtils.isToStringMethod(method)) {
582+
return String.format("DisposableBean proxy for SessionFactory [%s]", rawSf.toString());
583+
}
584+
if (method.equals(DisposableBean.class.getMethod("destroy"))) {
585+
closeHibernateSessionFactory(SessionFactoryBuilderSupport.this, rawSf);
586+
rawSf.close();
587+
return null;
588+
}
589+
return method.invoke(rawSf, args);
590+
}
591+
});
565592
}
566593

567594
/**
@@ -1409,4 +1436,24 @@ public static LobHandler getConfigTimeLobHandler() {
14091436
return configTimeLobHandlerHolder.get();
14101437
}
14111438

1439+
static void closeHibernateSessionFactory(SessionFactoryBuilderSupport<?> builder, SessionFactory sessionFactory) {
1440+
builder.logger.info("Closing Hibernate SessionFactory");
1441+
DataSource dataSource = builder.getDataSource();
1442+
if (dataSource != null) {
1443+
// Make given DataSource available for potential SchemaExport,
1444+
// which unfortunately reinstantiates a ConnectionProvider.
1445+
SessionFactoryBuilderSupport.configTimeDataSourceHolder.set(dataSource);
1446+
}
1447+
try {
1448+
builder.beforeSessionFactoryDestruction();
1449+
}
1450+
finally {
1451+
sessionFactory.close();
1452+
if (dataSource != null) {
1453+
// Reset DataSource holder.
1454+
SessionFactoryBuilderSupport.configTimeDataSourceHolder.remove();
1455+
}
1456+
}
1457+
}
1458+
14121459
}

org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/annotation/AnnotationSessionFactoryBean.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import org.hibernate.HibernateException;
2020
import org.hibernate.SessionFactory;
21+
2122
import org.springframework.context.ResourceLoaderAware;
2223
import org.springframework.core.io.ResourceLoader;
2324
import org.springframework.core.io.support.ResourcePatternUtils;
@@ -134,6 +135,11 @@ public void setPersistenceExceptionTranslator(
134135
delegate.setPersistenceExceptionTranslator(hibernateExceptionTranslator);
135136
}
136137

138+
@Override
139+
public SessionFactory wrapSessionFactoryIfNecessary(SessionFactory rawSf) {
140+
return delegate.wrapSessionFactoryIfNecessary(rawSf);
141+
}
142+
137143
/**
138144
* @deprecated as of Spring 3.1 in favor of {@link #scanPackages()} which
139145
* can access the internal {@code AnnotationConfiguration} instance via

0 commit comments

Comments
 (0)