Delayed SessionFactory Creation in Grails
The topic of delaying DataSource
and SessionFactory
creation until some point after startup has come up a few times on the Grails user mailing list so I thought I’d give it a shot. I got it working, but it’s not pretty.
Grails (and Hibernate) will create up to three connections during initialization so the primary focus is to avoid those. In addition the DataSource
will pre-instantiate connections, so we’ll delay those as well.
The first connection required is for HibernateDialectDetectorFactoryBean
, which uses connection metadata to figure out the dialect if it’s not specified. This is a cool feature but problematic if you don’t want early access to the database, and luckily the fix is simple: specify the dialect class in DataSource.groovy
:
dataSource { pooled = ... driverClassName = ... username = ... password = ... dialect = org.hibernate.dialect.MySQLInnoDBDialect }
substituting the appropriate class name for your database.
The second connection will be for SpringLobHandlerDetectorFactoryBean
, which uses connection metadata to determine if you’re using Oracle and if so to use an Oracle-specific LobHandler
. The fix here is also simple: override the lobHandlerDetector
bean in grails-app/conf/spring/resources.groovy
with the correct version for your database. It’s a little different if you’re using Oracle or another database.
If you’re not using Oracle, redefine the bean as
import org.springframework.jdbc.support.lob.DefaultLobHandler beans = { lobHandlerDetector(DefaultLobHandler) }
and if you are, define it as
import org.springframework.jdbc.support.nativejdbc.CommonsDbcpNativeJdbcExtractor import org.springframework.jdbc.support.lob.OracleLobHandler beans = { lobHandlerDetector(OracleLobHandler) { nativeJdbcExtractor = new CommonsDbcpNativeJdbcExtractor() } }
and omit specifying nativeJdbcExtractor
if you’re not using pooled connections (e.g. if you’re using JNDI).
The third connection is for Hibernate and is used to initialize the Configuration
. This one is more work and requires some custom code. It’s also somewhat brittle in that it requires a copy/paste of the sessionFactory
bean definition from Grails source with some modifications, so it will probably require changes to work with future version of Grails.
Here’s the override for Grails 1.2:
import org.codehaus.groovy.grails.orm.hibernate.events.PatchedDefaultFlushEventListener import com.burtbeckwith.grails.delayds.DelayedSessionFactoryBean ... sessionFactory(DelayedSessionFactoryBean) { def application = AH.application def ds = application.config.dataSource def hibConfig = application.config.hibernate dataSource = ref('dataSource') List hibConfigLocations = [] if (application.classLoader.getResource('hibernate.cfg.xml')) { hibConfigLocations < < 'classpath:hibernate.cfg.xml' } def explicitLocations = hibConfig?.config?.location if (explicitLocations) { if (explicitLocations instanceof Collection) { hibConfigLocations.addAll(explicitLocations.collect { it.toString() }) } else { hibConfigLocations << hibConfig.config.location.toString() } } configLocations = hibConfigLocations if (ds.configClass) { configClass = ds.configClass } hibernateProperties = ref('hibernateProperties') grailsApplication = ref('grailsApplication', true) lobHandler = ref('lobHandlerDetector') entityInterceptor = ref('entityInterceptor') eventListeners = ['flush': new PatchedDefaultFlushEventListener(), 'pre-load': ref('eventTriggeringInterceptor'), 'post-load': ref('eventTriggeringInterceptor'), 'save': ref('eventTriggeringInterceptor'), 'save-update': ref('eventTriggeringInterceptor'), 'post-insert': ref('eventTriggeringInterceptor'), 'pre-update': ref('eventTriggeringInterceptor'), 'post-update': ref('eventTriggeringInterceptor'), 'pre-delete': ref('eventTriggeringInterceptor'), 'post-delete': ref('eventTriggeringInterceptor')] }
and here’s the override for 1.1:
import com.burtbeckwith.grails.delayds.DelayedSessionFactoryBean ... sessionFactory(DelayedSessionFactoryBean) { def application = AH.application def ds = application.config.dataSource dataSource = ref('dataSource') if (application.classLoader.getResource('hibernate.cfg.xml')) { configLocation = 'classpath:hibernate.cfg.xml' } if (ds.configClass) { configClass = ds.configClass } hibernateProperties = ref('hibernateProperties') grailsApplication = ref('grailsApplication', true) lobHandler = ref('lobHandlerDetector') eventListeners = ['pre-load': ref('eventTriggeringInterceptor'), 'post-load': ref('eventTriggeringInterceptor'), 'save': ref('eventTriggeringInterceptor'), 'save-update': ref('eventTriggeringInterceptor'), 'post-insert': ref('eventTriggeringInterceptor'), 'pre-update': ref('eventTriggeringInterceptor'), 'post-update': ref('eventTriggeringInterceptor'), 'pre-delete': ref('eventTriggeringInterceptor'), 'post-delete': ref('eventTriggeringInterceptor')] }
DelayedSessionFactoryBean
extends ConfigurableLocalSessionFactoryBean
to create a wrapper for the SessionFactory
that lazily creates the real SessionFactory
. Add this to src/groovy
:
package com.burtbeckwith.grails.delayds import java.lang.reflect.Field import java.lang.reflect.InvocationHandler import java.lang.reflect.Method import java.lang.reflect.Proxy import org.codehaus.groovy.grails.orm.hibernate.ConfigurableLocalSessionFactoryBean import org.hibernate.SessionFactory import org.springframework.util.ReflectionUtils class DelayedSessionFactoryBean extends ConfigurableLocalSessionFactoryBean { private boolean _initialized private SessionFactory _realSessionFactory @Override void afterPropertiesSet() { // do nothing for now, lazy init on first access } @Override SessionFactory getObject() { def invoke = { proxy, Method method, Object[] args -> initialize() return method.invoke(_realSessionFactory, args) } return Proxy.newProxyInstance(SessionFactory.classLoader, [SessionFactory] as Class[], [invoke: invoke] as InvocationHandler) } private synchronized void initialize() { if (_initialized) { return } _realSessionFactory = wrapSessionFactoryIfNecessary(buildSessionFactory()) Field field = ReflectionUtils.findField(getClass(), 'sessionFactory') field.accessible = true field.set(this, _realSessionFactory) afterSessionFactoryCreation() _initialized = true } }
To delay DataSource
creation, we’ll use Spring’s DelegatingDataSource
and build the actual DataSource
from the values in grails-app/conf/DataSource.groovy
the first time getConnection()
is called:
package com.burtbeckwith.grails.delayds import java.sql.Connection import java.sql.SQLException import org.apache.commons.dbcp.BasicDataSource import org.codehaus.groovy.grails.commons.ConfigurationHolder as CH import org.springframework.jdbc.datasource.DelegatingDataSource class DelayedDataSource extends DelegatingDataSource { private boolean _initialized @Override Connection getConnection() throws SQLException { initialize() return super.getConnection() } @Override void afterPropertiesSet() { // override to not check for targetDataSource since it's lazily created } private synchronized void initialize() { if (_initialized) { return } def config = CH.config.dataSource setTargetDataSource(new BasicDataSource( driverClassName: config.driverClassName, password: config.password, username: config.username, url: config.url)) _initialized = true } }
This also requires an override in grails-app/conf/spring/resources.groovy
:
import com.burtbeckwith.grails.delayds.DelayedDataSource ... beans = { ... dataSource(DelayedDataSource) ... }
This creates a BasicDataSource
which is what Grails will use by default, but of course feel free to change it to c3p0 or some other provider.
The net effect of using these classes and overridden Spring bean definitions is that both the DataSource
and SessionFactory
will be lazily initialized on first use. One option might be to have an initialization page that accepts configuration overrides for the database url, username, etc. to allow an admin to start the app and choose the appropriate settings.
The source files shown here are available at DelayedSessionFactoryBean and DelayedDataSource
, and grab resources.groovy
as a sample to merge into your own
resources.groovy
.
[…] This post was Twitted by Sternegross […]
[…] An Army of Solipsists » Blog Archive » Delayed SessionFactory Creation in Grails […]
Burt,
I followed all the steps stated in the Delayed SessionFactory implementation.
When I run my app (deployed on Tomcat), I am getting the following error during an DML(Insert/Update/Delete)
operation. But the Read works fine. Essentially I am seeing data on my Form pulled from Oracle, but any change will result in this error.
Exception Message: No Hibernate Session bound to thread, and configuration does not allow creation of non-transactional one here
Am I missing something?
I am using Grails: 1.2.0 and app runs against Oracle DB.
Prakash
Burt,
in your code, ‘classpath:hibernate.cfg.xml’ and ‘explicitLocations’ are added to hibConfigLocations, how about domains in the directory of grails? Can you tell me how to include those domains?
Thanks in advance.
Roby
PS: i am using grails 1.3.5