Несколько источников данных, вызывающих 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, хотя...