Grails / GORM:避免嵌套保存调用的ConcurrentModificationException

时间:2018-01-03 16:50:48

标签: 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()参数的每个排列。这要么没有区别,要么推迟持久性,以便检查历史长度的断言语句失败。我还尝试用User.withSession { Session session -> session.flush() }手动刷新会话;这会抛出相同的ConcurrentModificationException

有什么我想念的吗?我怎样才能实现我想要做的事情?或者有人可以提出另一种方法吗?

1 个答案:

答案 0 :(得分:0)

我设法通过执行以下操作来解决此问题:

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

现在我的测试和我的代码都正常工作。 (好吧,至少在这方面)