【问题标题】:Grails/GORM: avoiding ConcurrentModificationException on nested save callsGrails/GORM:避免嵌套保存调用上的 ConcurrentModificationException
【发布时间】:2018-01-03 16:50:47
【问题描述】:

我正在使用一个有很多怪癖的遗留数据库,这是我尝试构建一个与 Grails 2.5.6 一起使用的数据访问库的最新障碍。

我有一个User 域类,它不是在数据库中拥有自己的表,而是通过UserHistory 类将新记录保存到历史表中。在历史表中,最新记录由changeType 属性指示:每个用户恰好有一条记录为空changeType,这是最新的。因此,为了保存User 对象,必须使用非空changeType 更新当前最新记录,并且必须在changeType 为空的情况下插入新记录。

我遇到的问题是,当我保存User 对象时,它会抛出一个ConcurrentModificationException,显然是在执行为刷新注册的操作时。更多详情如下。

以下是所讨论的两个类的概要:

// 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$1.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$1.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 参数的所有排列。这要么没有区别,要么推迟持久化,使得检查历史长度的断言语句失败。我还尝试使用User.withSession { Session session -&gt; session.flush() } 手动刷新会话;这会抛出相同的ConcurrentModificationException

我有什么遗漏吗?我怎样才能实现我正在尝试做的事情?或者还有其他人可以建议的方法吗?

【问题讨论】:

  • 你试过 currentHistory.save(flush : true) 吗?
  • beforeUpdate 中,不要从域对象的集合中获取currentHistory,而是尝试从数据库中获取它,例如UserHistory.findByPidmAndChangeType(...) 然后更新检索到的记录
  • @NitinDhomse 我确实尝试过。我在对save() 的三个调用中尝试了flush 的所有排列。
  • @MikeW 在实施我的修复之前我没有看到你的评论(见答案)。它现在正在运行,所以我不愿意再胡思乱想了,但你的想法可能会奏效。

标签: hibernate grails grails-orm


【解决方案1】:

我通过以下方式设法解决了这个问题:

  • newHistory 添加到User 的历史记录集合中
  • flush: false保存两个历史记录
  • flush: true保存用户

所以看起来不刷新嵌套保存和刷新外部保存的组合是正确的,但我的测试用例仍然失败,因为它正在查看缓存的集合而不是从数据库中重新获取它。

这是更新后的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
}

现在我的测试和代码都可以正常工作了。 (嗯,至少在这方面)

【讨论】:

    猜你喜欢
    • 2019-05-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-02-05
    • 2013-08-01
    • 1970-01-01
    • 2013-12-07
    • 1970-01-01
    相关资源
    最近更新 更多