hibernate - Grails/GORM:避免在嵌套保存调用上发生ConcurrentModificationException

标签 hibernate grails gorm

我正在使用一个有很多怪癖的旧数据库,这是我尝试构建可与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)

我已经尝试了对flush的三个调用中的save()参数的每个排列。这要么没有影响,要么推迟了持久性,以至于检查历史长度的assert语句失败。我也尝试过使用User.withSession { Session session -> session.flush() }手动刷新 session ;这将引发相同的ConcurrentModificationException

有什么我想念的吗?如何实现我要执行的操作?还是有人可以建议另一种方法?

最佳答案

通过执行以下操作,我设法解决了这一问题:

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

    现在我的测试和代码都可以正常工作。 (至少在这个区域)

    关于hibernate - Grails/GORM:避免在嵌套保存调用上发生ConcurrentModificationException,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48081696/

    相关文章:

    Java 持久性复合 ID

    hibernate - Grails:hasMany没用……我认为我的 hibernate session 做错了

    tomcat - support.PluginAwareResourceBundleMessageSource 无法解析插件的任何资源

    java - 如何正确设置 permgen 大小?

    date - Grails 域类日期

    hibernate - Grails 2.5至Grails 3.2 GORM 6

    sql - to_char函数在Hibernate中引发异常

    java - Spring LocalSessionFactoryBean 无法与 Annontaions 和 HBM 映射一起使用

    java - 使用 hibernate 更新数据库中的大量行

    hibernate - Apache derby in-memory db schema dump with Hibernate 4+