1

I am using Grails 2.4.4. I would like to test the persistence in Unit Test Class with an in-memory database. I have a Parent class with a oneToMany relation with Child. The Child is ownedBy the Parent and has a composite key involving the parent. When I try to delete one of the Children in the collection inside Parent, I get an error if I flush, no delete is fired if I omit the 'flush: true' parameter.

class Parent implements Serializable {
    String name

    static hasMany = [children : Child]

    static mapping = { 
        children cascade: 'all-delete-orphan'
    }
}

class OtherParent implements Serializable {
    String name
}

class Child implements Serializable {
    String name

    static belongsTo = [ owner : Parent, secondOwner : OtherParent]

    static mapping = {        
        id composite : ['owner', 'secondOwner']
    }

}

I would like to test relations in unit test classes annotated like this

@Domain([Parent, OtherParent, Child])
@TestMixin(HibernateTestMixin)
class ChildSpec extends Specification {

        def "Parents and Children can be created, saved and deleted"() {
            given: "we have a clean database at the start"
                Parent.count() == 0
                OtherParent.count() == 0
                Child.count() == 0

            and:
                Parent a = new Parent()
                a.name = "Parent"
                OtherParent secondParent = new OtherParent ()
                secondParent.name = 'Second Parent'         
                Child b = new Child()
                b.name = "Child"
                b.otherOwner = secondParent
                a.addToChildren(b)

            when: "we save Parent"
                secondParent.save(flush: true, failOnError: true)
                a.save(flush: true, failOnError: true)

            then: "Parent saves and Child is saved too"
                Parent.count() == 1
                Child.count() == 1
                def savedA = Parent.findByName("Parent")
                savedA.name == "Parent"
                savedA.children.size() == 1
                def savedB = savedA.children.getAt(0)
                savedB.name == "Child"
                def foundB = Child.findByName("Child")
                foundB.name == "Child"

            when: "we remove Child from Parent, we can still save Parent"
                savedA.removeFromChildren(savedB)
                savedB.delete(flush: true)
                savedA.save(failOnError: true, flush: true)

            then: "we've got an Parent with no Bs and no exception is thrown"
                notThrown(Exception)
                Child.count() == 0
        }
}

But an Exception is thrown

Expected no exception of type 'java.lang.Exception' to be thrown, but got it nevertheless
    at spock.lang.Specification.notThrown(Specification.java:106)
    at eu.europa.ec.comp.redda.test.ParentSpec.Parents and Chilren can be created, saved and deleted(ParentSpec.groovy:56)
Caused by: org.springframework.orm.hibernate4.HibernateOptimisticLockingFailureException: Object of class [eu.europa.ec.comp.redda.test.Child] with identifier [eu.europa.ec.comp.redda.test.Child : (unsaved)]: optimistic locking failed; nested exception is org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect) : [eu.europa.ec.comp.redda.test.Child#eu.europa.ec.comp.redda.test.Child : (unsaved)]
    at org.springframework.orm.hibernate4.SessionFactoryUtils.convertHibernateAccessException(SessionFactoryUtils.java:200)
    at org.codehaus.groovy.grails.orm.hibernate.GrailsHibernateTemplate.convertHibernateAccessException(GrailsHibernateTemplate.java:593)
    at org.codehaus.groovy.grails.orm.hibernate.GrailsHibernateTemplate.doExecute(GrailsHibernateTemplate.java:183)
    at org.codehaus.groovy.grails.orm.hibernate.GrailsHibernateTemplate.execute(GrailsHibernateTemplate.java:123)
    at org.codehaus.groovy.grails.orm.hibernate.InstanceApiHelper.delete(InstanceApiHelper.java:36)
    at org.codehaus.groovy.grails.orm.hibernate.HibernateGormInstanceApi.delete(HibernateGormInstanceApi.groovy:228)
    at eu.europa.ec.comp.redda.test.ParentSpec.Parents and Chilren can be created, saved and deleted(ParentSpec.groovy:52)
Caused by: org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect) : [eu.europa.ec.comp.redda.test.Child#eu.europa.ec.comp.redda.test.Child : (unsaved)]
    at org.hibernate.persister.entity.AbstractEntityPersister.check(AbstractEntityPersister.java:2541)
    at org.hibernate.persister.entity.AbstractEntityPersister.delete(AbstractEntityPersister.java:3403)
    at org.hibernate.persister.entity.AbstractEntityPersister.delete(AbstractEntityPersister.java:3630)
    at org.hibernate.action.internal.EntityDeleteAction.execute(EntityDeleteAction.java:114)
    at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:463)
    at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:349)
    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:1222)
    at org.codehaus.groovy.grails.orm.hibernate.InstanceApiHelper$1.doInHibernate(InstanceApiHelper.java:40)
    at org.codehaus.groovy.grails.orm.hibernate.InstanceApiHelper$1.doInHibernate(InstanceApiHelper.java:36)
    at org.codehaus.groovy.grails.orm.hibernate.GrailsHibernateTemplate.doExecute(GrailsHibernateTemplate.java:179)
    ... 4 more
Andrea
  • 335
  • 3
  • 12
  • What do you mean "I would like to test relations in unit test classes" please? I can't see what you're trying to achieve in your test. – John Mar 03 '15 at 08:45
  • I would like, in a Unit Test class, to see if I can correctly persist my domain classes without errors. The problem is that when I run my test, if I just call the save method with no parameters, then the B is not removed from the database. The B in the collection is deleted from the database only if I call save method with flush:on. But if I do so, a StaleObjectStateException Exception is thrown. (I want to see the delete statement in the logs, without getting an error) – Andrea Mar 03 '15 at 10:26
  • 1
    OK - where is your code for the setup for the test? i.e. the code that will allow you to do this `A.findByName(A_NAME)`? Also, what are you wanting to do here: `def b = a.bs.find`? – John Mar 03 '15 at 10:35
  • I changed my question a little adding properties and changing the classes' name. According to John answer and just to be more clear. – Andrea Mar 03 '15 at 11:09
  • UPDATED: Added Exception StackTrace and Complete Example based on John answer. – Andrea Mar 04 '15 at 09:00

1 Answers1

0

Your unit test needs some data to be set up for your test. If you want to test that you can create and save your objects, then try this (you're also missing a name property for the A domain class, and let's also assume there's one for the B class too):

def "As and Bs can be created, saved and deleted"() {
    expect: "we have a clean database at the start"
        A.count() == 0
        B.count() == 0

    given:
        A a = new A()
        a.name = "A"
        B b = new B()
        b.name = "B"
        a.addToBs(b)

    when: "we save A"
        a.save(flush: true, failOnError: true)

    then: "A saves and B is saved too"
        A.count() == 1
        B.count() == 1
        def savedA = A.findByName("A")
        savedA.name == "A"
        savedA.bs.size() == 1
        def savedB = savedA.bs.getAt(0)
        savedB.name == "B"
        def foundB = B.findByName("B")
        foundB.name == "B"

    when: "we remove B from A, we can still save A"
        savedA.removeFromBs(savedB)
        savedB.delete(flush: true)
        savedA.save(flush: true)

    then: "we've got an A with no Bs and no exception is thrown"
        notThrown(Exception)
        savedA.bs.count() == 0
}

EDIT to reflect change to the question:

Your domain model means that a Child must belong to both a Parent and an OtherParent; it is not an either/or relationship. Are you sure this is what you want?

If it is what you want, I still don't understand why you want a composite id? Also, you haven't included OtherParent in the @Domain annotation. The code here will work if you remove the composite id mapping.

John
  • 10,837
  • 17
  • 78
  • 141
  • The fact that you do all the test inside the same method, instead of saving objects in a method then retrieving them in another method, is relevant? – Andrea Mar 03 '15 at 10:56
  • 1
    Do you have a method in your test that creates/saves your objects that you then call from your test method? If yes, then by default a unit test case is transactional, meaning that you'll get a transaction for that other method (and I suspect they may be separate transactions, not nested). If you are then using the object from the second method in the first method, this may explain why you're getting an exception. – John Mar 03 '15 at 11:33
  • @ John: This answer helped me a lot and it put me on the good track, but the problem was elsewhere. In fact I oversimplified (my bad) the model and omitted the composite id on the Child class. I understand that Gorm has problems with composite ids and I really don't understand why. I will change my question according to the real model. SORRY, and thank you very much for your time – Andrea Mar 03 '15 at 15:31
  • A Child must have both a Parent and OtherParent fields, not null. A Parent can only have one Child in common with OtherParent. When I delete a Parent I want all its children to be deleted in the database. The code in the test will work if I manually delete the child from the parent's collection and then delete the child itself. If I use grails method 'deleteFrom*' an Exception is thrown (I think because Child 'id' field is null and Grails thinks that it is a transient object). – Andrea Mar 04 '15 at 08:59