Не могу найти, где запрос сделать нетерпеливую загрузку связи один-ко-многим - PullRequest
0 голосов
/ 23 января 2019

Я использую Grails v3.3.9.

Я не могу получить запросы для активной загрузки связи один-ко-многим. Перепробовал всевозможные способы.

Это сообщение относится к попытке использовать его в модульном тестировании (также не работает в приложении во время выполнения).

Сценарий. У меня есть два класса доменов, один из которых называется OrgRoleInstance с Collection<Site> сайтами и static hasMany =[sites:Site], а другой называется 'Site' с static belongsTo = [org:OrgRoleInstance].

Это перенаправленный один-ко-многим от организаций к сайтам.

Я создаю новый модульный тест, используя новую черту DomainUnitTest. В тестовой настройке я создаю три организации и добавляю по одному сайту каждый, затем добавляю один последний сайт в 3-ю организацию (org "C"). Программа установки работает нормально, и все экземпляры сохраняются.

В тесте запроса where я ищу организацию, которая имеет 2 сайта (это org "C").

Когда я запускаю тест в режиме отладки, запрос where возвращает массив размера 1, содержащий org "C", поэтому тест в предложении where может видеть, что семейство сайтов не равно нулю - пока все хорошо.

Я также делаю прямой Site.get (4), чтобы получить последний сайт. Я делаю проверку assert, чтобы убедиться, что ссылка sites.org - это тот же экземпляр, который был возвращен из запроса where. Это правда и проходит.

Однако, когда вы смотрите на запись orgs [0], коллекция сайтов пуста. Это простое 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

Обновление / наблюдения

Я включил ведение журнала 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, тогда функция fetchMode на корневом уровне в withCriteria будет работать теперь. Наконец, просто сделайте пустое закрытие в коллекции, чтобы вызвать активный запрос.

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]

Поэтому я подозреваю, что критерии модульного тестированияQueries или где запросы с использованием новых grails DomainUnitTest<T> черта не поддерживает запросы соединения / ожидания и т. Д.

Возможно, вы вынуждены проводить интеграционные тесты с реальной БД для проверки запросов к таблицам. Если кто-то может категорически заявить, что новые черты модульного тестирования не работают для запросов на соединение / нетерпение, которые могут мне помочь.

1 Ответ

0 голосов
/ 24 января 2019

Результатом этого исследования является то, что вы не можете использовать модульное тестирование для запросов модели домена, которые пытаются объединить таблицы с использованием новых признаков модульного тестирования.

Если вы хотите сделать запростестирование вас имеет , чтобы сделать это в качестве интеграционных тестов.

Когда я на этот раз перекодировал свои запросы в качестве интеграционных тестов, позволяя загружать данные начальной загрузки, которые загружаются перед любыми данными в настройке спока (), тогда запросы начинают работать и включают в себя активные запросы на данные.

Следует помнить, что метод 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.

Интеграционные тесты более трудоемки и дорогостоящи во времени.

Было бы неплохо (и умно), если бы черты модульного тестирования могли быть улучшены для поддержки тестирования запросов.

...