Grails/GORM:避免在嵌套保存调用时出现 ConcurrentModificationException
Grails/GORM: avoiding ConcurrentModificationException on nested save calls
我正在使用一个有很多怪癖的遗留数据库,这是我尝试构建一个数据访问库以使用 Grails 2.5.6 的最新障碍。
我有一个 User
域 class,它不是在数据库中有自己的 table,而是通过 UserHistory
class。在历史table中,最新记录由changeType
属性表示:每个用户只有一条记录为空changeType
,这是最新的。因此,为了保存 User
对象,必须使用非空 changeType
更新当前最新记录,并且必须插入新记录 changeType
为空。
我遇到的问题是,当我保存 User
对象时,它抛出一个 ConcurrentModificationException
,显然是在执行为刷新注册的操作时。更多详情如下。
这里是有问题的两个 classes 的概要:
// User.groovy
class User {
static mapping = {
id name: 'pidm', generator: 'assigned'
}
Long pidm
String firstName
@Lazy
Set<UserHistory> histories = { UserHistory.findAllByPidm(pidm) }()
def beforeUpdate()
{
// Mark the current UserHistory object with the appropriate change type
UserHistory currentHistory = histories.find({ it.changeType == null })
currentHistory.changeType = 'N'
// Create a new UserHistory object with the changes applied
UserHistory newHistory = new UserHistory(pidm: pidm, firstName: firstName)
// Save the two UserHistory objects.
currentHistory.save()
newHistory.save(insert: true)
// Return false so we don't try to save the User
return false
}
}
// UserHistory.groovy
class UserHistory {
Long pidm
String firstName
}
我的集成测试如下所示:
// UserIntegrationSpec.groovy
void "test saving a name change"()
{
when:
def user = User.get(pidm)
then:
user.firstName == oldName
when:
def oldHistoryCount = user.histories.size()
user.firstName = newName
user.save() // throws exception
def user2 = User.read(pidm)
then:
user2.firstName == newName
user2.histories.size() == oldHistoryCount + 1
where:
pidm | oldName | newName
123 | "David" | "Barry"
}
在 beforeUpdate()
执行之后但在 user.save()
完成之前抛出异常:
Failure: |
test saving a name change(UserIntegrationSpec)
|
java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)
at java.util.ArrayList$Itr.next(ArrayList.java:851)
at java.util.Collections$UnmodifiableCollection.next(Collections.java:1042)
at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:463)
at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:351)
at org.hibernate.event.internal.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:350)
at org.hibernate.event.internal.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:56)
at org.hibernate.internal.SessionImpl.flush(SessionImpl.java:1258)
at org.codehaus.groovy.grails.orm.hibernate.metaclass.SavePersistentMethod.flushSession(SavePersistentMethod.java:87)
at org.codehaus.groovy.grails.orm.hibernate.metaclass.SavePersistentMethod.doInHibernate(SavePersistentMethod.java:60)
at org.codehaus.groovy.grails.orm.hibernate.GrailsHibernateTemplate.doExecute(GrailsHibernateTemplate.java:188)
at org.codehaus.groovy.grails.orm.hibernate.GrailsHibernateTemplate.execute(GrailsHibernateTemplate.java:132)
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:215)
at org.codehaus.groovy.grails.orm.hibernate.metaclass.AbstractDynamicPersistentMethod.invoke(AbstractDynamicPersistentMethod.java:69)
at org.codehaus.groovy.grails.orm.hibernate.HibernateGormInstanceApi.save(HibernateGormInstanceApi.groovy:196)
at UserIntegrationSpec.test saving a name change(UserIntegrationSpec.groovy:43)
我已经尝试了对 save()
的三个调用中 flush
参数的所有排列。这要么没有区别,要么推迟持久化,以至于检查历史长度的 assert 语句失败。我还尝试使用 User.withSession { Session session -> session.flush() }
手动刷新会话;这抛出相同的 ConcurrentModificationException
.
有什么我想念的吗?我怎样才能实现我想做的事情?或者有人可以建议另一种方法吗?
我通过执行以下操作设法解决了这个问题:
- 将
newHistory
添加到 User
的 collection 历史记录中
- 用
flush: false
保存两条历史记录
- 用
flush: true
保存用户
所以看起来不刷新嵌套保存和刷新外部保存的组合是正确的,但我的测试用例仍然失败,因为它正在查看缓存的 collection 而不是重新获取它来自数据库。
这是更新后的 beforeUpdate
方法:
def beforeUpdate()
{
// Mark the current UserHistory object with the appropriate change type
UserHistory currentHistory = histories.find({ it.changeType == null })
currentHistory.changeType = 'N'
// Create a new UserHistory object with the changes applied
UserHistory newHistory = new UserHistory(pidm: pidm, firstName: firstName)
histories.add(newHistory) // this line is the key to the solution
// Save the two UserHistory objects.
currentHistory.save(flush: false)
newHistory.save(flush: false, insert: true)
// Return false so we don't try to save the User
return false
}
现在我的测试和代码都可以正常工作了。 (好吧,至少在这方面)
我正在使用一个有很多怪癖的遗留数据库,这是我尝试构建一个数据访问库以使用 Grails 2.5.6 的最新障碍。
我有一个 User
域 class,它不是在数据库中有自己的 table,而是通过 UserHistory
class。在历史table中,最新记录由changeType
属性表示:每个用户只有一条记录为空changeType
,这是最新的。因此,为了保存 User
对象,必须使用非空 changeType
更新当前最新记录,并且必须插入新记录 changeType
为空。
我遇到的问题是,当我保存 User
对象时,它抛出一个 ConcurrentModificationException
,显然是在执行为刷新注册的操作时。更多详情如下。
这里是有问题的两个 classes 的概要:
// User.groovy
class User {
static mapping = {
id name: 'pidm', generator: 'assigned'
}
Long pidm
String firstName
@Lazy
Set<UserHistory> histories = { UserHistory.findAllByPidm(pidm) }()
def beforeUpdate()
{
// Mark the current UserHistory object with the appropriate change type
UserHistory currentHistory = histories.find({ it.changeType == null })
currentHistory.changeType = 'N'
// Create a new UserHistory object with the changes applied
UserHistory newHistory = new UserHistory(pidm: pidm, firstName: firstName)
// Save the two UserHistory objects.
currentHistory.save()
newHistory.save(insert: true)
// Return false so we don't try to save the User
return false
}
}
// UserHistory.groovy
class UserHistory {
Long pidm
String firstName
}
我的集成测试如下所示:
// UserIntegrationSpec.groovy
void "test saving a name change"()
{
when:
def user = User.get(pidm)
then:
user.firstName == oldName
when:
def oldHistoryCount = user.histories.size()
user.firstName = newName
user.save() // throws exception
def user2 = User.read(pidm)
then:
user2.firstName == newName
user2.histories.size() == oldHistoryCount + 1
where:
pidm | oldName | newName
123 | "David" | "Barry"
}
在 beforeUpdate()
执行之后但在 user.save()
完成之前抛出异常:
Failure: |
test saving a name change(UserIntegrationSpec)
|
java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)
at java.util.ArrayList$Itr.next(ArrayList.java:851)
at java.util.Collections$UnmodifiableCollection.next(Collections.java:1042)
at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:463)
at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:351)
at org.hibernate.event.internal.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:350)
at org.hibernate.event.internal.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:56)
at org.hibernate.internal.SessionImpl.flush(SessionImpl.java:1258)
at org.codehaus.groovy.grails.orm.hibernate.metaclass.SavePersistentMethod.flushSession(SavePersistentMethod.java:87)
at org.codehaus.groovy.grails.orm.hibernate.metaclass.SavePersistentMethod.doInHibernate(SavePersistentMethod.java:60)
at org.codehaus.groovy.grails.orm.hibernate.GrailsHibernateTemplate.doExecute(GrailsHibernateTemplate.java:188)
at org.codehaus.groovy.grails.orm.hibernate.GrailsHibernateTemplate.execute(GrailsHibernateTemplate.java:132)
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:215)
at org.codehaus.groovy.grails.orm.hibernate.metaclass.AbstractDynamicPersistentMethod.invoke(AbstractDynamicPersistentMethod.java:69)
at org.codehaus.groovy.grails.orm.hibernate.HibernateGormInstanceApi.save(HibernateGormInstanceApi.groovy:196)
at UserIntegrationSpec.test saving a name change(UserIntegrationSpec.groovy:43)
我已经尝试了对 save()
的三个调用中 flush
参数的所有排列。这要么没有区别,要么推迟持久化,以至于检查历史长度的 assert 语句失败。我还尝试使用 User.withSession { Session session -> session.flush() }
手动刷新会话;这抛出相同的 ConcurrentModificationException
.
有什么我想念的吗?我怎样才能实现我想做的事情?或者有人可以建议另一种方法吗?
我通过执行以下操作设法解决了这个问题:
- 将
newHistory
添加到User
的 collection 历史记录中 - 用
flush: false
保存两条历史记录
- 用
flush: true
保存用户
所以看起来不刷新嵌套保存和刷新外部保存的组合是正确的,但我的测试用例仍然失败,因为它正在查看缓存的 collection 而不是重新获取它来自数据库。
这是更新后的 beforeUpdate
方法:
def beforeUpdate()
{
// Mark the current UserHistory object with the appropriate change type
UserHistory currentHistory = histories.find({ it.changeType == null })
currentHistory.changeType = 'N'
// Create a new UserHistory object with the changes applied
UserHistory newHistory = new UserHistory(pidm: pidm, firstName: firstName)
histories.add(newHistory) // this line is the key to the solution
// Save the two UserHistory objects.
currentHistory.save(flush: false)
newHistory.save(flush: false, insert: true)
// Return false so we don't try to save the User
return false
}
现在我的测试和代码都可以正常工作了。 (好吧,至少在这方面)