Delayed SessionFactory Creation in Grails - No Fluff Just Stuff

Delayed SessionFactory Creation in Grails

Posted by: Burt Beckwith on January 25, 2010

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.

Burt Beckwith

About Burt Beckwith

Burt Beckwith has been a software developer for 15 years, most of that as a JVM developer, and for the last five years working with Grails and Groovy. He is a core developer on the Grails team at SpringSource, and has created over 40 Grails plugins. Burt is a frequent speaker at conferences and user groups where he shares his passion for Grails and other Groovy-based technologies, in particular those that are related to persistence, security, and performance. He is the author of “Programming Grails” and blogs at http://burtbeckwith.com/blog/

Why Attend the NFJS Tour?

  • » Cutting-Edge Technologies
  • » Agile Practices
  • » Peer Exchange

Current Topics:

  • Languages on the JVM: Scala, Groovy, Clojure
  • Enterprise Java
  • Core Java, Java 8
  • Agility
  • Testing: Geb, Spock, Easyb
  • REST
  • NoSQL: MongoDB, Cassandra
  • Hadoop
  • Spring 4
  • Cloud
  • Automation Tools: Gradle, Git, Jenkins, Sonar
  • HTML5, CSS3, AngularJS, jQuery, Usability
  • Mobile Apps - iPhone and Android
  • More...
Learn More »