无法获取查询的地方,急切加载一对多关联

问题描述 投票:1回答:1

我使用的是Grails v3.3.9。

我无法获得查询以急切地加载一对多关联。尝试过各种各样的方式。

这是相对于尝试在单元测试中使用它而发布的(在运行时也在应用程序上失败)。

场景:我有两个域类,一个名为'OrgRoleInstance',其中包含Collection<Site>站点,static hasMany =[sites:Site],另一个名为'Site',它有一个static belongsTo = [org:OrgRoleInstance]

这是从orgs到站点的双向一对多。

我使用新的DomainUnitTest特性创建一个新的单元测试。在测试设置中,我创建三个组织并分别添加一个站点,然后我将最后一个站点添加到第三个组织(组织“C”)。安装程序正常,所有实例都保持不变。

在查询测试的地方,我寻找一个有2个站点的组织(org“C”)。

当我在debug中运行测试时,where查询返回一个包含org“C”的大小为1的数组,因此where子句中的测试可以看到sites集合不为null - 到目前为止一直很好。

我也直接使用Site.get(4)来获取最后一个站点。我做一个断言检查,检查sites.org ref是否从where查询返回相同的实例。这是真的并且通过了。

但是,当您查看orgs [0]条目时,sites collection为null。这个简单的println因访问查询返回的null sites属性而失败。

class OrgRoleInstanceSpec extends Specification implements DomainUnitTest<OrgRoleInstance> {


    def setup() {
        OrgRoleInstance

        List<OrgRoleInstance> orgs = []
        ["A","B","C"].each {
            OrgRoleInstance org = new OrgRoleInstance(name:it, role:OrgRoleInstance.OrgRoleType.Customer)
            org.addToSites(new Site( name: "$it's Head Office", status:"open", org:org))
            orgs << org

        }
        orgs[2].addToSites (new Site( name: "${orgs[2].name}'s Branch Office", status:"open", org:orgs[2]))
        OrgRoleInstance.saveAll(orgs)
        assert OrgRoleInstance.count() == 3
        println "# of sites : " + Site.count()
        assert Site.count() == 4
        assert Site.get(2).org.id == orgs[1].id
    }


    void "where query and individual get " () {
        given :

        def orgs = OrgRoleInstance.where {
            sites.size() == 2
        }.list(fetch:[sites:"eager"])

             def org = OrgRoleInstance.get(2)
            List orgSites = org.sites

            def branch = Site.get(4)

            assert branch.org.is (orgs[0]) //assert is true

            println orgs[0].sites[1].name  //orgs[0].sites is null !


        expect:
        orgs.size() == 1

    }

}

我已经尝试过与标准,基本的findAll(fetch:[sites:"eager")等。

但是我尝试这个我无法获得查询以返回热切的填充网站集合。

我想在查询中而不是通过OrgeRoleInstance域类中的mapping子句来执行此操作

更新/意见

我启用了SQL日志记录并启动了grails控制台。然后我输入以下脚本(使用我的引导数据)。

手动键入的控制台脚本

import com.softwood.domain.*

def orgs = OrgRoleInstance.where {
 id == 4
 sites{}
 }.list() /* (fetch:[sites:"eager"]) */

println orgs[0].sites

运行时输出以下内容 - 基本上似乎运行急切选择 - 我使用内部联接获取orgs.sites结果与填充的条目:

groovy> import com.softwood.domain.* 
groovy> def orgs = OrgRoleInstance.where { 
groovy>  id == 4 
groovy>  sites{} 
groovy>  }.list() /* (fetch:[sites:"eager"]) */ 
groovy> println orgs[0].sites 

2019-01-23 14:02:00.923 DEBUG --- [      Thread-18] org.hibernate.SQL                        : 
    select
        this_.id as id1_16_1_,
        this_.version as version2_16_1_,
        this_.role as role3_16_1_,
        this_.name as name4_16_1_,
        sites_alia1_.id as id1_21_0_,
        sites_alia1_.version as version2_21_0_,
        sites_alia1_.org_id as org_id3_21_0_,
        sites_alia1_.name as name4_21_0_,
        sites_alia1_.status as status5_21_0_ 
    from
        org_role_instance this_ 
    inner join
        site sites_alia1_ 
            on this_.id=sites_alia1_.org_id 
    where
        this_.id=?
2019-01-23 14:02:00.923 TRACE --- [      Thread-18] o.h.type.descriptor.sql.BasicBinder      : binding parameter [1] as [BIGINT] - [4]
2019-01-23 14:02:00.923 DEBUG --- [      Thread-18] org.hibernate.SQL                        : 
    select
        sites0_.org_id as org_id3_21_0_,
        sites0_.id as id1_21_0_,
        sites0_.id as id1_21_1_,
        sites0_.version as version2_21_1_,
        sites0_.org_id as org_id3_21_1_,
        sites0_.name as name4_21_1_,
        sites0_.status as status5_21_1_ 
    from
        site sites0_ 
    where
        sites0_.org_id=?
2019-01-23 14:02:00.923 TRACE --- [      Thread-18] o.h.type.descriptor.sql.BasicBinder      : binding parameter [1] as [BIGINT] - [4]
[Site:(name : 1 Barkley Square) belonging to org: com.softwood.domain.OrgRoleInstance : 4, Site:(name : 10 South Close) belonging to org: com.softwood.domain.OrgRoleInstance : 4]

那么为什么这不适用于单元测试呢?

另一个更新

我回到了grails控制台并设法让这个标准查询工作。第1点 - 你必须导入org.hibernate.FetchMode,然后在withCriteria闭包的根级别的fetchMode函数现在可以工作。最后,只需对集合执行空闭包即可强制执行急切查询。

import com.softwood.domain.*
import org.hibernate.FetchMode

def orgs = OrgRoleInstance.withCriteria {
            fetchMode ("sites", FetchMode.SELECT)


            sites{}
        }

println orgs[0].sites

但是,这在单元测试中不起作用。像这样的单元测试中的相同查询

void "criteria query " () {
    given:

    OrgRoleInstance org

    org = OrgRoleInstance.withCriteria (uniqueResult: true) {
        fetchMode ("sites", FetchMode.SELECT)

        sites{}
    }

    expect:
    org.id == 3
    org.sites.size() == 2

}

失败并出现此错误

groovy.lang.MissingMethodException: No signature of method: grails.gorm.CriteriaBuilder.fetchMode() is applicable for argument types: (java.lang.String, org.hibernate.FetchMode) values: [sites, SELECT]

因此我怀疑单元测试criteriaQueries或使用新grails DomainUnitTest<T> trait的查询不支持join / eager查询等。

也许您被迫与真实数据库集成测试来测试跨表的查询。如果任何人都可以明确声明新的单元测试特征不适用于可能对我有帮助的加入/急切查询。

unit-testing grails eager-loading
1个回答
0
投票

此调查的结果是,您不能对尝试使用新单元测试特征连接表的域模型查询使用单元测试。

如果要查询测试,则必须将其作为集成测试。

当我将查询重新编码为集成测试时,允许在spock setup()方法中的任何数据之前加载的引导数据,然后查询开始工作并包括对数据的急切查询。

要记住的一件事是setup()方法不进行回滚(以便数据保留在整个测试中),因此如果数据库没有被删除,您应该在最后进行手动清理。

所以这没有编码为集成测试,似乎工作!

package com.softwood.domain

import grails.testing.mixin.integration.Integration
import grails.transaction.*
import org.hibernate.FetchMode
import org.hibernate.LazyInitializationException
import spock.lang.Shared
import spock.lang.Specification

@Integration
@Rollback
class OrgRoleInstanceIntegSpecSpec extends Specification {

    //operates on separate transaction thats not rolled back
    @Shared
    List<OrgRoleInstance> orgs = []

    @Shared
    List<OrgRoleInstance> sites = []

    @Shared
    NetworkDomain netDomain

    @Shared
    def bootstrapPreExistingOrgsCount, bootstrapPreExistingSitesCount

    //runs in transaction thats not rolled back for each test
    def setup() {
        def site

        bootstrapPreExistingOrgsCount = OrgRoleInstance.count()
        bootstrapPreExistingSitesCount = Site.count()
        //println "pre exist orgs:$boostrapPreExistingOrgsCount + pre exist sites: $boostrapPreExistingSitesCount"
        assert bootstrapPreExistingOrgsCount == 4
        assert bootstrapPreExistingSitesCount == 2

        ["A","B","C"].each {
            OrgRoleInstance org = new OrgRoleInstance(name:"test$it", role:OrgRoleInstance.OrgRoleType.Customer)
            org.addToSites(site = new Site( name: "test$it's Head Office", status:"open", org:org))
            orgs << org
            sites << site

        }
        orgs[2].addToSites (site = new Site( name: "${orgs[2].name}'s Branch Office", status:"open", org:orgs[2]))
        orgs[2].addToDomains(netDomain = new NetworkDomain (name:"corporate WAN", customer:[orgs[2]]))
        sites << site

        OrgRoleInstance.saveAll(orgs)
        assert orgs.size() == 3
        assert sites.size() ==4
        assert OrgRoleInstance.count() == 3 + bootstrapPreExistingOrgsCount
        assert Site.count() == 4 + bootstrapPreExistingSitesCount
        assert Site.get(4).org.id == orgs[1].id
        println "setup integration test data"

    }

    //manual cleanup of integration test data
    def cleanup() {

        orgs.each {OrgRoleInstance org ->
            org.sites.each {it.delete()}
            org.delete(flush:true)
            assert OrgRoleInstance.exists(org.id) == false
        }
        println "deleted integration test data"
    }

    void "Orgs list with eager fetch query"() {
        given :

        def orgs = OrgRoleInstance.list(fetch:[sites:"eager"])
        println "org ${orgs[6].name} sites : " + orgs[5].sites

        println "test site #2  has org as : " + (Site.list())[3].org

        expect :
        Site.count() == 4 + bootstrapPreExistingSitesCount
        orgs.size() == 3 + bootstrapPreExistingOrgsCount
        orgs[5].getName() == "testB"
        orgs[5].sites.size() == 1

    }

    void "orgs where query triggering eager site get"() {
        given :

        //where clause returns instance of DetachedCriteria, so have to trigger with a list/get etc
        def orgs = OrgRoleInstance.where {
            name =~ "%testB%" &&
                    sites{}
        }.list()

        expect :
        orgs.size() == 1
        orgs[0].name == "testB"
        orgs[0].sites.size() == 1

    }

    void "withCriteria query with eager site fetch (two selects)  " () {
        given:

        OrgRoleInstance org

        //with criteria runs the query for you unlike createCriteria() which returns  a detachedCriteria
        org = OrgRoleInstance.withCriteria (uniqueResult: true) {
            fetchMode ("sites", FetchMode.SELECT)
            idEq(7L)  //internally wont cast Integer to long, so set it explicitly
            sites{}
        }

        /*def orgs = OrgRoleInstance.withCriteria {
            //setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY)
            eq 'name', "B"
            //fetchMode 'sites', FetchMode.SELECT
            sites{}
        }*/

        expect:
        org.id == 7
        org.sites.size() == 2

    }

    void "detached criteria (with distinct) with eager fetch " () {
        given:


        def orgs = OrgRoleInstance.createCriteria().listDistinct {
            //fetchMode 'sites', FetchMode.SELECT
            join 'sites'
            sites {
                org {
                    eq 'id', 6L
                }
            }
        }

        def site = orgs[0].sites[0]

        expect:
        orgs.size() == 1
        orgs[0].sites.size() == 1
        site.name == "testB's Head Office"

    }

    void "where query on id only without list (fetch eager) " () {
        given :

        def orgs = OrgRoleInstance.where {
            id == 7L
        }.list ()

        def branch = orgs[0].sites[0]

        when:

        println "branch "+ branch.name  //try and access branch

        then:
        /*
        -- seems to do an eager fetch on sites+domains, even though i didnt ask it to
         not quite sure why - separate exploration round that i think
         */
        //LazyInitializationException ex = thrown()
        orgs.size() == 1
        orgs[0].sites.size() == 2
        orgs[0].domains.size() == 1

    }

}

我希望这可以使其他人免于为什么你的测试不起作用的心痛。

另请注意,运行grails控制台将启动控制台脚本应用程序但使用它启动gorm构建 - 因此明智地使用包含域包 - 您可以针对gorm启动时加载的任何引导数据以交互方式尝试一些测试查询。

在运行时,集成测试更加费力且成本更高。

如果可以增强单元测试特性以支持查询测试,那将是很好的(而且很聪明)。

© www.soinside.com 2019 - 2024. All rights reserved.