Несколько источников данных, вызывающих HibernateException?

Для моего проекта grails у меня есть база данных h2 в памяти для большинства моих классов, но я просто добавил постоянную базу данных для всех классов, связанных с пользователями. При запуске приложение выполняет несколько потоков. После добавления второго источника данных я получаю HibernateExceptions. В частности,

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

Они происходят только для первых одного или двух потоков, а остальные выполняются без проблем. Это наводит меня на мысль, что это какая-то проблема параллелизма с первым запуском базы данных. Код, где происходит ошибка:

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

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

Я понятия не имею, если это проблема с базой данных, спящий режим или что-то еще. Бег Грааля 2.0.4

Полная 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)

Мои источники данных:

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"
        }
    }
}

2 ответа

Решение

Мне удалось подавить исключения с помощью довольно небрежного обходного пути. Надеюсь, это поможет кому-то еще, но если у кого-то есть другое решение или какое-либо понимание проблемы, я хотел бы услышать это.

В любом случае, вы хотите вызывать merge(), а не save(), когда это уместно. Я не знаю, как определить, имеется ли ссылка на экземпляр в нескольких сеансах, поэтому я просто обернул каждое сохранение в блок try-catch, а затем вызвал merge(), если есть исключение. Это не самое красивое решение, но оно работает.

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() }                    
}

В Hibernate каждый PersistedCollection (класс-обертка, который Hibernate устанавливает для любых ваших отношений коллекции) связан только с одним Session пример. Это часть работы Hibernate, чтобы убедиться, что Session гарантии уровня идентичности экземпляра поддерживаются.

Получаемая ошибка указывает на то, что вы загрузили объект с коллекцией в одном Session, а затем попытался .save() это в другом Session, Это может не сработать, если вы сначала не сделаете шаг, связав эту сущность с другой Sessionчто вы могли бы сделать, позвонив otherSession.merge(entity), Вот почему ваш обходной путь "работает".

Вам следует взглянуть на этот раздел документации Hibernate, который касается репликации из одного источника данных в другой. Я не уверен, если replicate доступно в Ruby/Grails, хотя...

Другие вопросы по тегам