Home Grails/GORM: avoiding ConcurrentModificationException on nested save calls
Reply: 0

Grails/GORM: avoiding ConcurrentModificationException on nested save calls

user4968
1#
user4968 Published in April 24, 2018, 8:13 am

I'm working with a legacy database that has many quirks, and this is the latest snag in my attempt to build a data access library to work with Grails 2.5.6.

I have a User domain class that, rather than having its own table in the database, saves new records to a historical table via a UserHistory class. In the historical table, the latest record is indicated by a changeType property: there is exactly one record per user with a null changeType, and that's the latest. So in order to save a User object the current latest record must be updated with a non-null changeType and a new record must be inserted with changeType being null.

The problem I encounter is that when I save the User object, it throws a ConcurrentModificationException, apparently when going through the actions registered for the flush. More details below.

Here is an outline of the two classes in question:

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

My integration test looks like:

// 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"
}

An exception is thrown after beforeUpdate() executes, but before user.save() finishes:

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)

I have tried every permutation of the flush parameter on the three calls to save(). This either makes no difference or else defers the persisting such that the assert statement checking the history length fails. I've also tried manually flushing the session with User.withSession { Session session -> session.flush() }; this throws the same ConcurrentModificationException.

Is there something I'm missing? How can I implement what I'm trying to do? Or is there another approach someone can suggest?

You need to login account before you can post.

About| Privacy statement| Terms of Service| Advertising| Contact us| Help| Sitemap|
Processed in 0.307437 second(s) , Gzip On .

© 2016 Powered by mzan.com design MATCHINFO