Search code examples
hibernatesessiongrailshibernateexception

Multiple datasources causing HibernateException?


For my grails project, I have an in-memory h2 database for most of my classes, but just added a persistent database for all User-related classes. On startup, the app executes several threads. After adding the second datasource, I'm getting HibernateExceptions. Specifically,

Illegal attempt to associate a collection with two open sessions; nested exception is org.hibernate.HibernateException

They only happen for the first one or two threads, then the rest execute without issue. This leads me to believe it's some sort of concurrency issue with the database first starting up. The code where the error happens is:

instance.customer = customerService.retrieveCustomer instance
instance.name = instance.customer?.name

Instance.withTransaction{
    instance.customer.save()  // <-- THIS LINE IS THE PROBLEM
    instance.save()
}

I have no idea if this is a database issue, hibernate issue, or something else. Running Grails 2.0.4

Full Stacktrace:

| Error 2013-03-20 16:25:30,995 [pool-16-thread-1] ERROR kindlingcustomers.InstanceList  - k_54 Illegal attempt to associate a collection with two open sessions; nested exception is org.hibernate.HibernateException: Illegal attempt to associate a collection with two open sessions org.springframework.orm.hibernate3.HibernateSystemException: Illegal attempt to associate a collection with two open sessions; nested exception is org.hibernate.HibernateException: Illegal attempt to associate a collection with two open sessions
    at org.springframework.orm.hibernate3.SessionFactoryUtils.convertHibernateAccessException(SessionFactoryUtils.java:683)
    at org.springframework.orm.hibernate3.HibernateAccessor.convertHibernateAccessException(HibernateAccessor.java:412)
    at org.springframework.orm.hibernate3.HibernateTemplate.doExecute(HibernateTemplate.java:411)
    at org.springframework.orm.hibernate3.HibernateTemplate.execute(HibernateTemplate.java:339)
    at org.codehaus.groovy.grails.orm.hibernate.metaclass.SavePersistentMethod.performSave(SavePersistentMethod.java:56)
    at org.codehaus.groovy.grails.orm.hibernate.metaclass.AbstractSavePersistentMethod.doInvokeInternal(AbstractSavePersistentMethod.java:212)
    at org.codehaus.groovy.grails.orm.hibernate.metaclass.AbstractDynamicPersistentMethod.invoke(AbstractDynamicPersistentMethod.java:63)
    at org.codehaus.groovy.grails.commons.metaclass.DynamicMethodInvocation$invoke.call(Unknown Source)
    at org.codehaus.groovy.grails.orm.hibernate.HibernateGormInstanceApi.save(HibernateGormEnhancer.groovy:847)
    at kindlingcustomers.Customer.save(Customer.groovy)
    at kindlingcustomers.Customer$save$0.call(Unknown Source)
    at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:42)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:108)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:112)
    at kindlingcustomers.InstanceList$__clinit__closure5_closure8.doCall(InstanceList.groovy:67)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:597)
    at com.springsource.loaded.ri.ReflectiveInterceptor.jlrMethodInvoke(ReflectiveInterceptor.java:1231)
    at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:90)
    at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:233)
    at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1047)
    at groovy.lang.ExpandoMetaClass.invokeMethod(ExpandoMetaClass.java:1110)
    at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:877)
    at groovy.lang.Closure.call(Closure.java:412)
    at kindlingcustomers.InstanceList$__clinit__closure5_closure8.call(InstanceList.groovy)
    at org.codehaus.groovy.runtime.ConvertedClosure.invokeCustom(ConvertedClosure.java:51)
    at org.codehaus.groovy.runtime.ConversionHandler.invoke(ConversionHandler.java:82)
    at com.sun.proxy.$Proxy30.doInTransaction(Unknown Source)
    at org.springframework.transaction.support.TransactionTemplate.execute(TransactionTemplate.java:130)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:597)
    at com.springsource.loaded.ri.ReflectiveInterceptor.jlrMethodInvoke(ReflectiveInterceptor.java:1231)
    at org.codehaus.groovy.runtime.callsite.PojoMetaMethodSite$PojoCachedMethodSite.invoke(PojoMetaMethodSite.java:189)
    at org.codehaus.groovy.runtime.callsite.PojoMetaMethodSite.call(PojoMetaMethodSite.java:53)
    at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:42)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:108)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:116)
    at org.grails.datastore.gorm.GormStaticApi.withTransaction(GormStaticApi.groovy:573)
    at kindlingcustomers.Instance.withTransaction(Instance.groovy)
    at kindlingcustomers.Instance$withTransaction.call(Unknown Source)
    at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:42)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:108)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:116)
    at kindlingcustomers.InstanceList$__clinit__closure5.doCall(InstanceList.groovy:66)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:597)
    at com.springsource.loaded.ri.ReflectiveInterceptor.jlrMethodInvoke(ReflectiveInterceptor.java:1231)
    at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:90)
    at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:233)
    at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1047)
    at groovy.lang.ExpandoMetaClass.invokeMethod(ExpandoMetaClass.java:1110)
    at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:877)
    at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:921)
    at groovy.lang.ExpandoMetaClass.invokeMethod(ExpandoMetaClass.java:1110)
    at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:877)
    at groovy.lang.Closure.call(Closure.java:412)
    at groovy.lang.Closure.call(Closure.java:406)
    at groovy.lang.Closure.run(Closure.java:490)
    at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:895)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:918)
    at java.lang.Thread.run(Thread.java:680)

My Datasources:

environments {
    development {
        dataSource {
            dbCreate = "create-drop" // one of 'create', 'create-drop', 'update', 'validate', ''
            url = "jdbc:h2:mem:devDb;MVCC=TRUE"
        }

        dataSource_users {
            dbCreate = "update" 
            url = "jdbc:h2:devDb;MVCC=TRUE;LOCK_TIMEOUT=10000"
        }
    }
    test {
        dataSource {
            dbCreate = "update"
            url = "jdbc:h2:mem:testDb;MVCC=TRUE"
        }

        dataSource_users {
            dbCreate = "update"
            url = "jdbc:h2:testDb;MVCC=TRUE;LOCK_TIMEOUT=10000"
        }
    }
    production {
        dataSource {
            dbCreate = "create-drop"
            url = "jdbc:h2:mem;MVCC=TRUE"
            pooled = true
            properties {
               maxActive = -1
               minEvictableIdleTimeMillis=1800000
               timeBetweenEvictionRunsMillis=1800000
               numTestsPerEvictionRun=3
               testOnBorrow=true
               testWhileIdle=true
               testOnReturn=true
               validationQuery="SELECT 1"
            }
        }

        dataSource_users {
            dbCreate = "update"
            url = "jdbc:h2:prodDb;MVCC=TRUE;LOCK_TIMEOUT=10000"
        }
    }
}

Solution

  • I managed to suppress the exceptions with a pretty sloppy workaround. Hopefully it will help someone else coming along, but if anyone has another solution or any insight into the problem, I would love to hear it.

    Anyway, you want to call merge() rather than save() when appropriate. I don't know how to determine if an instance is referenced across multiple sessions, so I just wrapped each save in a try-catch block and then call merge() if there's an exception. It's not the prettiest solution but it works.

    def customerService = AH.application.mainContext.customerService
    instance.customer = customerService.retrieveCustomer instance
    instance.name = instance.customer?.name ?: instance.domain?.tokenize('.')[0].capitalize() ?: ''
    
    Instance.withTransaction{
        try{ instance.customer.save() } 
        catch (Exception e) { instance.customer.merge() }
    
        try { instance.save() } 
        catch (Exception e) { instance.merge() }                    
    }