0

我创建了两个简单的 Grails V3 域类,其中位置是父 Venue 中的嵌入属性类型,如下所示

import java.time.LocalDate

class Venue {

    String name
    LocalDate dateCreated
    LocalDate lastVisited
    LocalDate lastUpdated
    GeoAddress location

    static hasOne = [location:GeoAddress]

    static embedded =['location']

    static constraints = {
        lastVisited nullable:true
        location    nullable:true
    }
    static mapping = {
        location cascade: "all-delete-orphan", lazy:false  //eager fetch strategy

    }
}


class   GeoAddress {

    String addressLine1
    String addressLine2
    String addressLine3
    String town
    String county
    String country = "UK"
    String postcode

    static belongsTo = Venue

    static constraints = {
        addressLine1 nullable:true
        addressLine2 nullable:true
        addressLine3 nullable:true
        town         nullable:true
        county       nullable:true
        country      nullable:true
        postcode     nullable:true
    }
}

但是,当我编写集成测试时 - 我发现位置的级联创建不起作用(我必须在传递到场地之前保存它不再是瞬态的位置。

此外,当我在启用了 flush:true 的场地上运行删除并查询地址时,我仍然得到返回的嵌入式地址 - 我认为使用 flush:true 我会看到我的 GeoAddress 级联删除,但我的测试失败了如我所料,使用 GeoAddress.get(loc.id) 时不要得到空值

@Integration
@Rollback
class VenueIntegrationSpec extends Specification {
  void "test venue with an address" () {
        when: "create a venue and an address using transitive save on embedded "
            GeoAddress address = new GeoAddress (addressLine1: "myhouse", town: "Ipswich", county: "suffolk", postcode : "IP4 2TH")
            address.save()  //have to save first - else Venue save fails

            Venue v = new Venue (name: "bistro", location: address)
            def result = v.save()

        then: "retrieve venue and check its location loaded eagerly "
            Venue lookupVenue = Venue.get(v.id)
            GeoAddress loc = lookupVenue.location
            loc.postcode == "IP4 2TH"
            loc.town == "Ipswich"

        when: " we delete the venue, it deletes the embedded location (Address)"
            v.delete (flush:true)
            GeoAddress lookupLoc = GeoAddress.get (loc.id)

        then: "address should disppear"
            lookupLoc == null
    }

我以为我已经正确设置了这个,但显然我没有。为什么我对 Venue.save() 和 delete() 的级联操作不会级联到我的嵌入式位置 (GeoAddress) 条目?

4

4 回答 4

0

好的 - 我仔细阅读并寻找差异 - 如果我在像这样传递给场地构造函数之前保存嵌入的 GeoAddress 就会发生这种情况(修改后的简单测试)

当我添加额外的 a.save() 时,创建 GeoAddress 后,测试将失败。如果我评论保存并重新运行 - 它工作正常。不确定这是功能还是错误。场地应该进行传递保存,因为 GeoAddress 有一个 stati belongsTo = Venue 声明。

   //new test - reuse embedded  entity - works
    void "test with GeoLocation" () {
        when: ""
        GeoAddress a = new GeoAddress(town:"ipswich")
        a.save()
        Venue v = new Venue (name: "bistro", location: a)
        assert v.save(flush:true)

        Venue lookupVenue = Venue.get(v.id)

        GeoAddress ta = lookupVenue.location
        assert ta.town == "ipswich"

        //try delete
        v.delete (flush:true)


        then : " retrieve temp"
        GeoAddress.findAll().size() == 0
 }

如果有人可以为我评论错误与功能 - 那么如有必要,我可以在 grails 项目上提出错误以修复它。否则我只需要仔细测试并确保我在我的代码中做正确的事

于 2017-02-11T17:24:59.837 回答
0

如果我理解正确

cascade: "all-delete-orphan"

仅当您有hasMany=[something:Something]

在您的情况下,如果我要创建这样的关系,它是hasOneGeoAddress location可能是更好的设置。我知道两者之间存在细微差别。

无论如何,您正在测试所有理论。我认为您需要捕获错误才能开始弄清楚为什么它没有级联预期的行为。所以要么

if (!v.delete(flush:true) { 
  println "---  ${v.errors}" 
}

或将其包裹起来

尝试捕获块

. 我对 hasMany 关系也有类似的问题,这是由于底层 hasMany 表关系本身的设置而与其他表共享的记录。诀窍是从对象本身中删除条目:

lookupVenue .removeFromLocation(loc)

正如我所说,这是一个 hasMany 关系

于 2017-02-06T10:12:53.130 回答
0

非常奇怪,太累了,现在想不通。我尝试了外部实体和嵌入式 - 请参阅下面的调整模型。

我写了太新的测试,两者都有效 - 但原始测试没有。我在做一些奇怪的事情——只是没有发现它。这两个新测试执行相同的流程 - 只是变量不同 - 两者都有效。所以问题出在第一次测试上。

修改后的测试

@Integration
@Rollback
class VenueIntegrationSpec extends Specification {

    def setup() {
    }

    def cleanup() {
    }

    //original test -  this fails, have to explicitly delete loc to make it work 
    void "test venue with an address" () {
        when: "create a venue and an address using transitive save on embedded "
            GeoAddress address = new GeoAddress (addressLine1: "myhouse", town: "Ipswich", county: "suffolk", postcode : "IP4 2TH")
            address.save()
            Venue v = new Venue (name: "bistro", location: address)
            def result = v.save(flush:true)

        then: "retrieve venue and check its location loaded eagerly "
            Venue lookupVenue = Venue.get(v.id)
            GeoAddress loc = lookupVenue.location
            loc.postcode == "IP4 2TH"
            loc.town == "Ipswich"

        when: " we delete the venue, it deletes the embedded location (Address)"
            //loc.delete(flush:true)
            v.delete (flush:true)
            if (v.hasErrors())
                println "errors: $v.errors"

            GeoAddress lookupLoc = GeoAddress.get (loc.id)

        then: "address should disppear"
            Venue.get (v.id) == null
            lookupLoc == null
    }

    //new test - external entity - works 
    void "test with tempLocation" () {
        when: ""
            TempLocation temp = new TempLocation(name:"will")
            Venue v = new Venue (name: "bistro", temp: temp)
            assert v.save(flush:true)

            Venue lookupVenue = Venue.get(v.id)

            TempLocation t = lookupVenue.temp
            assert t.name == "will"

            //try delete
            v.delete (flush:true)


        then : " retrieve temp"
            TempLocation.findAll().size() == 0
    }

    //new test - reuse embedded  entity - works 
    void "test with GeoLocation" () {
        when: ""
        GeoAddress a = new GeoAddress(town:"ipswich")
        Venue v = new Venue (name: "bistro", location: a)
        assert v.save(flush:true)

        Venue lookupVenue = Venue.get(v.id)

        GeoAddress ta = lookupVenue.location
        assert ta.town == "ipswich"

        //try delete
        v.delete (flush:true)


        then : " retrieve temp"
        GeoAddress.findAll().size() == 0
    }
}

修改后的测试主题 - 带有嵌入 GeoAddress 的 Venue.groovy

class Venue {

    String name
    LocalDate dateCreated
    LocalDate lastVisited
    LocalDate lastUpdated
    GeoAddress location
    Collection posts

    //test behaviour
    TempLocation temp

    static hasOne = [location:GeoAddress, temp:TempLocation]
    static hasMany = [posts:Post]
    static embedded =['location']

    static constraints = {
        lastVisited nullable:true
        location    nullable:true, unique:true
        posts       nullable:true
        temp        nullable:true //remove later
    }
    static mapping = {
        location cascade: "all-delete-orphan", lazy:false, unique:true  //eager fetch strategy
        posts    sorted: "desc"
        temp     cascade: "all-delete-orphan", lazy:false, unique:true //remove later
    }
}


class   GeoAddress {

    String addressLine1
    String addressLine2
    String addressLine3
    String town
    String county
    String country = "UK"
    String postcode

    static belongsTo = Venue

    static constraints = {
        addressLine1 nullable:true
        addressLine2 nullable:true
        addressLine3 nullable:true
        town         nullable:true
        county       nullable:true
        country      nullable:true
        postcode     nullable:true
    }
}

用于破解的新外部地址/位置版本。具有相同 beongsTo/constraint 逻辑的简化版 geoAddress

class TempLocation {

    String name

    //setup birdiectional one to one, cascade owned on venue
    static belongsTo = [venue:Venue]

    static constraints = {
        name nullable:true
    }
}

将尝试在火车上重新阅读-不知道为什么第一次测试失败但接下来的两次工作正常....上床睡觉-太累了

于 2017-02-07T22:48:46.650 回答
0

我认为这是一个错误配置。嵌入意味着实体嵌入在域类中。通常这是一个普通的 POJO,位于 domainclass 文件夹之外(以及 src/groovy 文件夹中)。嵌入实体的所有字段都包含在嵌入实体的表中。hasone 设置两个域类实体之间的关系。所以要么使用嵌入式,要么使用 hasOne,但不要同时使用两者。

此外,级联保存深度嵌套的实体存在问题,这在 3.2.5 中得到解决。

于 2017-02-09T23:26:38.207 回答