Grails не может удалить сироту при сборе в модульных тестах, если используется составной идентификатор

Я использую Grails 2.4.4. Я хотел бы проверить постоянство в классе модульного тестирования с базой данных в памяти. У меня есть родительский класс с отношением oneToMany с Child. Ребенок принадлежит Родителю и имеет составной ключ с участием родителя. Когда я пытаюсь удалить одного из дочерних элементов в коллекции внутри Parent, я получаю сообщение об ошибке, если выполняется сброс, и удаление не запускается, если я опускаю параметр "flush: true".

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']
    }

}

Я хотел бы протестировать отношения в аннотированных классах

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

Но исключение брошено

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

1 ответ

Вашему модульному тесту необходимо настроить некоторые данные для вашего теста. Если вы хотите проверить, что вы можете создавать и сохранять свои объекты, попробуйте это (вы также пропускаете name свойство для класса домена A, и давайте также предположим, что оно есть и для класса B):

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
}

РЕДАКТИРОВАТЬ, чтобы отразить изменение в вопросе:

Ваша модель домена означает, что дочерний элемент должен принадлежать как родителю, так и другому родителю; это не / или отношения. Вы уверены, что это то, что вы хотите?

Если это то, что вы хотите, я до сих пор не понимаю, почему вы хотите составной идентификатор? Кроме того, вы не включили OtherParent в @Domain аннотаций. Код здесь будет работать, если вы удалите составное отображение идентификатора.

Другие вопросы по тегам