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:
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
beans = {
lobHandlerDetector(DefaultLobHandler)
}
and if you are, define it as
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 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:
...
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:
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:
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:
...
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.
About Burt Beckwith
Burt Beckwith is a Java and Groovy developer with over ten years of experience in a variety of industries including biotech, travel, e-learning, social networking, and financial services. For the past three years he's been working with Grails and Groovy full-time. Along the way he's created over fifteen Grails plugins and made significant contributions to several others. He was the technical editor for Grails in Action.
More About Burt »SpringOne 2GX
October 15 - 18, 2012
Washington DC
Current Topics on the NFJS Tour
- Core Java, JEE
- Dynamic Languages: Groovy, JRuby, Scala, Clojure
- RESTful Web Apps
- Frameworks: Hibernate, Grails, Spring, JSF, GWT, more
- Agility
- Test Driven Design
- Security
- Ajax, Flex, RIA
NFJS, the Magazine
2012-08-01 00:00:00.0 Issue Now AvailableFunctional SOLID, Part 1
by Matt StineThe Guts of Git: Hashing, Compression, DAGs, Trees, and Blobs
by Matthew McCulloughJava Modularity, Part 2
by Kirk KnoernschildUnderstanding Binary Compatibility
by Douglas Hawkins