0

In a Grails service, I create/update domain entities based on an external list. Updates take place in a loop. If the line in the input file is valid, entities are updated. Otherwise, I store the faulty line number and process the next one.

Problem: when I call validate() on an entity, if the result is false, any work done in the service in rolled back, even if does not pertain to the validation, and although no exception is visible. For instance:

assert contact.firstName = 'Bruce'
contact.firstName = 'John'
assert contact.save()
...
company.vat = 'bogus'
if (!company.validate()) {
    log.error "bogus"
    company.discard()
} else {
    assert company.save()
}
log.debug "done"

If company does not validate, my log will show "bogus" and "done". Contact first name will be 'Bruce' in the database, not 'John'.

Alternate version: if I do not call company.validate(), the contact first name is updated.

At this stage, I suspect Grails attaches my company instance upon the validate() call, dooming the transaction when it realizes validations do not pass, and despite the discard() effort.

From the validate() semantic and its javadoc ("Validates an instance"), I didn't expect any side effect, whether the validation fails or not, or whether I even invoke validate() or not.

What do you think? Is this a "normal" bahavior, or a bug? How can I work around this?

I have a simple reproduction case with two entities if needed.

youri
  • 3,685
  • 5
  • 23
  • 43

2 Answers2

0

I might be missing something here, but isn't the whole goal of the 'discard' method to not apply the change, and what you're seeing here (not applying the change in case of a validation error) would be logical?

Erik Pragt
  • 13,513
  • 11
  • 58
  • 64
  • Nope, I added the discard() in an attempt to counteract the dreaded validate() behavior. If the company() does not validate, I do not want to save it anyway so it's okay to discard. – youri Jan 14 '13 at 16:16
0

Not able to test it on this setup, but could you set transactional = false in your service, process all the objects (adding the correctly validated objects in one list and saving/logging the failed ones elsewhere), and then finally saving the "good" list of objects using withTransaction? Something like:

static transactional = false
def sessionFactory
List<Company> validCompanies = []
for(Company company: listOfCompaniesToValidate) {
    if (!company.validate()) {
        log.error "bogus"
    } else {
        validCompanies.add(company)
    }
}
Company.withTransaction {
    for (Company validCompany: validCompanies)
        validCompany.save()
    }
    validCompanies.clear()
}
sessionFactory.getCurrentSession().clear();

Just an idea!

  • Yes, this will work but of course it is very inelegant and still the validate semantic is incorrect. I might open a bug report... – youri Jan 30 '13 at 16:27