In the spirit of Rod Johnson’s naming conventions, I was not ashamed when I decided to name this class SessionFactoryPrototypeFactoryBean. Before I get into details, let me present the problem. Spring provides a factory bean out of the box to establish Hibernate SessionFactory instances, or, should I say instance. In fact, this class isn’t much of a factory at all. At its very best, it can create and handoff exactly one instance of a Hibernate SessionFactory. It acts as a transparent proxy to the SessionFactory, creating a new instance in the afterPropertiesSet
method and passing all subsequent method calls directly to the underlying SessionFactory. I offer a solution that will allow prototyping of the SessionFactory that this bean manages (saving you all some grief along the way).
So why the pain? Well, a business requirement arose in which we needed to dynamically switch between different instances of a SessionFactory backing the same database schema. There are two prime use cases calling for this functionality. In one case, it might be necessary to connect to a different regional data source depending on credentials or regionality of the user, or perhaps the same user needs to connect to databases of different regions in order to manage or report on them. The second case would be when a single application needs to access different environments in order to migrate data between them.
Let’s look at what is wrong with LocalSessionFactoryBean. First off, it hardcodes the return for the isSingleton
method to true
. This makes configuration as a prototype impossible. Naturally, a factory is a singleton, but in the case of a spring factory bean, the flag indicates how the underlying bean should be managed. So, no matter how the factory is configured, it will always return the same SessionFactory. The second problem is that the SessionFactory is created in the afterPropertiesSet
method. As everyone knows, SessionFactory instances take a long time to create and if the connection to the DataSource is slow, it makes matters worse (resulting in an unnecessary slow startup). Finally, because a Hibernate SessionFactory maps 1:1 with a DataSource, it is necessary to be able to notify the factory that the DataSource has changed. You might think that this is straightforward since there is a setDataSource on the LocalSessionFactoryBean. Not so. Since the LocalSessionFactoryBean passes all calls directly to SessionFactory, it is not possible to reconfigure it after initial load.
The solution comes in three parts. First, the return for the isSingleton
method is going to be reversed to true
(I suppose it could be made configurable, but I will leave that open for discussion and refactoring). The second change is to add a preinitialize flag, which will indicate whether the SessionFactory should be created eagerly, or whether it should be deferred until first use. Finally, the most significant change is to allow the DataSource to be looked up dynamically by providing a spring bean name which might have a factory-method or is otherwise made mutable (within a synchronized block of course).
If you notice, the SessionFactory is not created on each call to getObject
which would be typical for a prototype bean. The reason for this is that when the SessionFactory is wired up (which is when getObject
is called), we want it to remain the same for all consecutive calls. The use of this prototype would need to happen inside of a synchronized block so that the lookup method returns a consistent result for the duration of the wiring.
/**
* A factory bean that creates one or more Hibernate SessionFactory instances. This
* class identifies itself as a prototype (non-singleton) so that it can better manage the
* lifecycle of its managed SessionFactory instance, though it does not reinitialize on
* every call. Instead, it delegates to a data source lookup bean to retrieve the
* "current" data source, which must be managed in a synchronized block of code. If
* the data source has changed, then a new SessionFactory will be established and returned.
* At this point, the original SessionFactory reference will be dropped and the bean will
* act as a proxy to the new SessionFactory, available for wiring.
*/
public class SessionFactoryPrototypeFactoryBean extends LocalSessionFactoryBean implements BeanFactoryAware {
private BeanFactory beanFactory;
private DataSource previousDataSource;
private String dataSourceLookupName;
private boolean initialize = false;
public boolean isSingleton() {
return false;
}
public boolean isPreinitialize() {
return initialize;
}
public void setPreinitialize( boolean preinitialize ) {
this.initialize = preinitialize;
}
/**
* This method is called when the bean is initialized. In this case,
* the session factory is considered a prototype (non-singleton), so
* each time it is wired up, this method will be used. Hence, to
* achieve the same sessionFactory for a single wiring-chain, the
* underlying datasource is compared against a lookup datasource, which
* should remain consistent during wiring.
*/
public Object getObject() {
if ( dataSourceLookupName != null ) {
DataSource ds = lookupDataSource();
// notice we must deal with exact references here
if ( previousDataSource == null || !previousDataSource.equals( ds ) ) {
setDataSource( ds );
try {
afterPropertiesSet();
}
catch ( Exception e ) {
throw new BeanCreationException( "Failure initializing Hibernate SessionFactory", e );
}
}
}
return super.getObject();
}
public void setDataSource( DataSource dataSource ) {
previousDataSource = dataSource;
super.setDataSource( dataSource );
}
public void setDataSourceLookupName( String name ) {
this.dataSourceLookupName = name;
}
public void setBeanFactory( BeanFactory beanFactory ) throws BeansException {
this.beanFactory = beanFactory;
}
public void afterPropertiesSet() throws IllegalArgumentException, HibernateException, IOException {
// attempt a lookup if preinitialization is specified and the datasource wasn't explicitly set
if ( initialize && previousDataSource == null ) {
setDataSource( lookupDataSource() );
}
// preinitialize if specified and the data source is available
if ( initialize && previousDataSource != null ) {
super.afterPropertiesSet();
}
// the next time around, we will initialize
initialize = true;
}
// TODO: check if bean exists and all that jazz
private DataSource lookupDataSource() {
return (DataSource) beanFactory.getBean( dataSourceLookupName );
}
}
Below is a sample implementation which creates a new service instance that in turn uses the new SessionFactory.
...
public MyService getMyService( String id ) {
MyService service = null;
synchronized (this.serviceCache) {
service = this.serviceCache.get( id );
}
if ( service != null ) {
// ensure a bean isn't currently in creation (slipped through the synchronized block)
if (service == CURRENTLY_IN_CREATION) {
throw new LookupException( "Requested endpoint is currently in creation. Please retry operation." );
}
}
else {
synchronized( this.serviceCache ) {
// re-check within synchronized block
service = this.serviceCache.get( id );
if ( service == null ) {
this.serviceCache.put( id, CURRENTLY_IN_CREATION );
try {
// endpoint key is a local instance that is valid only for the duration of this call
// it is used to get a prototype instance of the data source when wiring the beans
dataSourceId = id;
// TODO: perhaps this bean name should be configurable
service = (MyService) beanFactory.getBean( "myService" );
serviceCache.put( id, service );
}
catch ( BeanCreationException e ) {
throw new LookupException( e.getMessage(), e );
}
}
}
}
return service;
}
...
public DataSource retrieveDataSource() {
return dataSources.get( dataSourceId );
}
...
And the spring beans file to wire it up.
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean id="sessionFactoryPrototype" class="SessionFactoryPrototypeFactoryBean">
<property name="dataSourceLookupName" value="dataSourceLookup" />
...
</bean>
<bean id="myService" class="MyServiceImpl" singleton="false">
<property name="myDao">
<bean class="MyDaoImpl">
<property name="sessionFactory"><ref local="sessionFactoryPrototype" /></property>
</bean>
</property>
<property name="dataSources">
<map>
<entry key="ds1">
<bean class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost/database1?user=user&password=password" />
</bean>
</entry>
<entry key="ds2">
<bean class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost/database2?user=user&password=password" />
</bean>
</entry>
</map>
</property>
</bean>
<bean id="dataSourceLookup" factory-bean="myService" factory-method="retrieveDataSource" singleton="false" />
</beans>
Mert Caliskan did something similar to use multiple data sources with the same Hibernate SessionFactory.