Grails
  1. Grails
  2. GRAILS-8922

Hibernate AssertionFailure when flushing with a new invalid child object in a hasMany or hasOne relationship

    Details

    • 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).

        Activity

        Hide
        Graeme Rocher added a comment -

        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().

        Show
        Graeme Rocher added a comment - 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().
        Hide
        Graeme Rocher added a comment -

        I have implemented a partial fix:

        https://github.com/grails/grails-core/commit/71af1320c617eac49203acfd7525ce605275f3a0

        what this does is set the inverse side to read-only as well. However your attached test cases still fail. This is because of the explicit session flush()

        In general you should not flush() the session after a validation error has occurred and this is what your code is doing. There is unfortunately no way for us to fix that case as it is internal to Hibernate.

        As for why this occurs on 2.0.x and not 1.3.x

        In 1.3.x changes could potentially be flushed to the database without the user knowing about them since we did not evict invalid objects during flush. This bug was fixed in 2.0.x, however a side effect is issues like this.

        You may need to revise your code slightly (as discussed above) to upgrade your application to Grails 2.0

        Show
        Graeme Rocher added a comment - I have implemented a partial fix: https://github.com/grails/grails-core/commit/71af1320c617eac49203acfd7525ce605275f3a0 what this does is set the inverse side to read-only as well. However your attached test cases still fail. This is because of the explicit session flush() In general you should not flush() the session after a validation error has occurred and this is what your code is doing. There is unfortunately no way for us to fix that case as it is internal to Hibernate. As for why this occurs on 2.0.x and not 1.3.x In 1.3.x changes could potentially be flushed to the database without the user knowing about them since we did not evict invalid objects during flush. This bug was fixed in 2.0.x, however a side effect is issues like this. You may need to revise your code slightly (as discussed above) to upgrade your application to Grails 2.0

          People

          • Assignee:
            Graeme Rocher
            Reporter:
            Michael Cameron
          • Votes:
            4 Vote for this issue
            Watchers:
            3 Start watching this issue

            Dates

            • Created:
              Updated:
              Resolved:

              Development