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.

5 Responses to “Delayed SessionFactory Creation in Grails”

  1. [...] This post was Twitted by Sternegross [...]

  2. [...] An Army of Solipsists » Blog Archive » Delayed SessionFactory Creation in Grails [...]

  3. Prakash says:

    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

  4. roby says:

    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

Leave a Reply

Creative Commons License
This work is licensed under a Creative Commons Attribution 3.0 License.