Details
-
Type:
Bug
-
Status:
Closed
-
Priority:
Blocker
-
Resolution: Fixed
-
Affects Version/s: 2.0.1
-
Fix Version/s: 2.0.2
-
Component/s: Persistence
-
Labels:
-
Environment:Java 1.6 b29, Mac OS X 10.7.3
-
Testcase included:yes
Description
An AssertionFailure will occur when flushing a session that contains a one-to-many (or one-to-one) relationship and the many-side (or child-side) has a new, but invalid, object. This works (the invalid objects are not saved, and no errors occurs) in 1.3.6. Flushing with existing child domain objects that already have an ID, but are now invalid, do not have an issue.
Example Domain Classes:
class Author {
String name
static hasMany = [books:Book]
}
class Book {
static belongsTo = [author:Author]
String name
static constraints = {
name(validator: {it != 'invalid'})
}
}
Then run the following code:
def author = new Author(name:'author1') author.save(failOnError:true) def book = new Book(author:author, name:'invalid') author.addToBooks(book) assert !book.save() ctx.sessionFactory.currentSession.flush()
The assertion will succeed (i.e. the book will not save), but the flush will fail with:
AssertionFailure - an assertion failure occured (this may indicate a bug in Hibernate, but is more likely due to unsafe use of the session) Message: null id in Book entry (don't flush the Session after an exception occurs)
It will also fail without any save, just having the new invalid book associated with the author will cause an exception on the next flush, regardless of what is causing the flush. I've been able to track it down a bit, it seems in Grails 1.3.6 the HibernateDomainClassValidator (Grails code) will set the parent object (Author) to read only which will make Hibernate 3.3 not cascade in it's AbstractFlushingEventListener.prepareEntityFlushes. This changed between Hibernate 3.3.x and 3.6.x so read only objects also cascade, though Grails 2.0.1 apparently does set invalid objects to read only anymore. What I need to discover, is how Grails is preventing a normal new invalid object from not participating in flushing, and why the behavior does not work in the one-to-many case (and possibly others).
Simply reorganizing your code fixes this issue. If your code was:
def author = new Author(name: 'author1') def book = new Book(author: author, name: 'invalid') author.addToBooks(book) author.save(failOnError: true, flush: true)Where you add the book to the author and then save then the issue doesn't exist. In this case Grails prevents the child object from being flushed by setting the parent to read-only during the call to author.save().