【问题标题】:Grails fails to delete orphan on collection in Unit tests, if composite id is used如果使用复合 id,Grails 无法在单元测试中删除孤立的集合
【发布时间】:2015-03-02 17:54:27
【问题描述】:

我正在使用 Grails 2.4.4。我想测试 具有内存数据库的单元测试类的持久性。 我有一个与 Child 有 oneToMany 关系的 Parent 类。 Child 由 Parent 拥有,并且具有涉及父级的复合键。当我尝试删除 Parent 内的集合中的一个 Children 时,如果我刷新,我会收到一个错误,如果我省略 '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

【问题讨论】:

  • 请问“我想在单元测试类中测试关系”是什么意思?我看不到您在测试中要达到的目标。
  • 我想在单元测试类中,看看我是否可以正确地保持我的域类没有错误。问题是当我运行我的测试时,如果我只调用不带参数的 save 方法,那么 B 不会从数据库中删除。只有当我使用 flush:on 调用 save 方法时,集合中的 B 才会从数据库中删除。但如果我这样做,则会引发 StaleObjectStateException 异常。 (想看日志中的delete语句,不报错)
  • OK - 您的测试设置代码在哪里?即允许您执行此操作的代码A.findByName(A_NAME)?另外,你想在这里做什么:def b = a.bs.find?
  • 我稍微改变了我的问题,添加了属性并更改了类的名称。根据约翰的回答,只是为了更清楚。
  • 更新:添加了异常 StackTrace 和基于 John 回答的完整示例。

标签: grails one-to-many composite-key all-delete-orphan


【解决方案1】:

您的单元测试需要为您的测试设置一些数据。如果你想测试你是否可以创建和保存你的对象,那么试试这个(你还缺少 A 域类的 name 属性,我们还假设 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
}

编辑以反映问题的变化:

您的域模型意味着 Child 必须同时属于 Parent 和 OtherParent;这不是非此即彼的关系。你确定这是你想要的吗?

如果是你想要的,我还是不明白你为什么要复合 id?此外,您还没有在 @Domain 注释中包含 OtherParent。如果您删除复合 id 映射,此处的代码将起作用。

【讨论】:

  • 您在同一个方法中进行所有测试,而不是在一个方法中保存对象然后在另一个方法中检索它们,这一事实是否相关?
  • 您的测试中是否有一个方法可以创建/保存您的对象,然后您可以从您的测试方法中调用这些对象?如果是,那么默认情况下单元测试用例是事务性的,这意味着您将获得该其他方法的事务(我怀疑它们可能是单独的事务,而不是嵌套的)。如果您在第一种方法中使用第二种方法中的对象,这可以解释为什么您会收到异常。
  • @John:这个答案对我帮助很大,它让我走上了正轨,但问题出在其他地方。事实上,我过度简化了(我的错)模型并省略了 Child 类的复合 id。我知道 Gorm 在复合 ID 方面存在问题,我真的不明白为什么。我将根据真实模型更改我的问题。抱歉,非常感谢您抽出宝贵时间
  • Child 必须同时具有 Parent 和 OtherParent 字段,不能为空。 Parent 只能与 OtherParent 有一个 Child。当我删除父级时,我希望在数据库中删除其所有子级。如果我从父母的集合中手动删除孩子,然后删除孩子本身,则测试中的代码将起作用。如果我使用 grails 方法 'deleteFrom*' 会引发异常(我认为是因为 Child 'id' 字段为 null 而 Grails 认为它​​是一个瞬态对象)。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-08-16
  • 1970-01-01
  • 2013-09-10
  • 1970-01-01
  • 1970-01-01
  • 2019-07-18
  • 2010-11-21
相关资源
最近更新 更多