Данные теста интеграции Grails не могут пересекать границы родного SQL и GORM

Цель

При выполнении интеграционных тестов я хотел бы заполнить базу данных предустановленными данными с помощью GORM.

Проблема

Для определенных доменов собственные запросы SQL не могут видеть данные, вставленные GORM, и наоборот (запросы GORM не могут видеть данные, вставленные собственным SQL). За один прогон у одного домена будет эта проблема, а у другого - нет.

Описание

Подробное описание см. в выходных данных консоли в нижней части этого сообщения. Думаю, подсказки к решению можно увидеть в выходных данных.

Проект: Grails 2.4.2 Источник данных:

    test {
    cehCode = "PR"
    schemaName = "AF"
    parallelSchemaName = "AF"
    dataSource {
        dbCreate = "create-drop"
        url = "jdbc:h2:mem:testDb;MVCC=TRUE;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE;INIT=CREATE SCHEMA IF NOT EXISTS AF"
        driverClassName = "org.h2.Driver"
        properties {
            initialSize = 2
            minIdle = 1
            maxIdle = 2
            maxActive = 2
            maxWait = 2000
            maxAge = 60000
            minEvictableIdleTimeMillis=30000
            timeBetweenEvictionRunsMillis=30000
            abandonWhenPercentageFull = 50
            numTestsPerEvictionRun=3
            testOnBorrow=true
            testWhileIdle=true
            testOnReturn=true
            validationQuery="SELECT 1"
            validationInterval=500
            //defaultTransactionIsolation = java.sql.Connection.TRANSACTION_READ_UNCOMMITTED
        }
    }

}

Интеграционный тест:

void "Test CUST filter"() {
    when: 'Build test data'
    then:
        assert testQueryService.testInsert() == "Done with inserts"
        assert testQueryService.testQuery() == "Done with queries"
}

Услуга:

package com.lrs.accrual.common

import com.lrs.contract.TFastCodes
import grails.transaction.Transactional
import groovy.sql.Sql
import com.lrs.accrual.IntermodalRatingAuth

@Transactional
class TestQueryService {
def sessionFactory

def testInsert() {
    println "----------------------START EXECUTING INSERTS ON TABLES------------------------------"
    println "********* START NEW TABLE/DOMAIN TFAST_CODES/TFastCodes *********"
    def nativeInsert = """INSERT INTO AF.TFAST_CODES (APPL_ELE,APPL_CD_VAL,APPL_CD_DESC,CAS_OWNER,ACS_TYPE_CD)
                VALUES ('NATIVE SQL2','1','ORPT-TEST      PR','ZZZZ','BU')"""
    def sql = new Sql(sessionFactory.getCurrentSession().connection())
    sql.executeInsert(nativeInsert)
    sql.commit()
    TFastCodes codes = new TFastCodes(applicationElement: 'GORM new 3', casOwner: 'ZZZZ', applicationCodeDescription: 'JUNK2', applicationCodeValue: '1', accessTypeCode: 'BU', newColumn2: 'Val')
    println "Valid TFastCodes domain? " + codes.validate()
    codes.save(flush: true)
    println "********* END NEW TABLE/DOMAIN *********"

    println "********* START OLD TABLE/DOMAIN TIMDL_RT_AUTH/IntermodalRatingAuth *********"
    def nativeInsert2 = """INSERT INTO AF.TIMDL_RT_AUTH (AGREEMENT_NUMB,MRKT_RATE_KEY,WB_RT_AUTH_REF)
                VALUES ('NATIVE',1,'2016-01-01-01.01.01.000000')"""
    def sql2 = new Sql(sessionFactory.getCurrentSession().connection())
    sql2.executeInsert(nativeInsert2)
    sql2.commit()
    IntermodalRatingAuth auth= new IntermodalRatingAuth(agreementNumber:"GORM", marketingRateKey:1, waybillRateAuthRef: "other string")
    println "Valid IntermodalRatingAuth domain? " + auth.validate()
    auth.save(flush: true)
    println "********* END OLD TABLE/DOMAIN *********"
    println "----------------------END EXECUTING INSERTS ON TABLES------------------------------"
    return "Done with inserts"
}

def testQuery() {
    println "----------------------START EXECUTING QUERIES ON TABLES------------------------------"
    println "********* START QUERIES AGAINST NEW TABLE/DOMAIN TFAST_CODES/TFastCodes *********"
    def codes = TFastCodes.list()
    codes?.eachWithIndex { val, i -> println "Gorm TFastCodes result $i: " + val.properties }
    println "Native TFAST_CODES query result: " + sessionFactory.getCurrentSession()?.createSQLQuery("SELECT * FROM AF.TFAST_CODES").list()
    println "********* END QUERIES AGAINST NEW TABLE/DOMAIN TFAST_CODES/TFastCodes *********"
    println "********* START QUERIES AGAINST OLD TABLE/DOMAIN TIMDL_RT_AUTH/IntermodalRatingAuth *********"
    def auths = IntermodalRatingAuth.list()
    auths?.eachWithIndex { val, i -> println "Gorm IntermodalRatingAuth result $i: " + val.properties }
    println "Native TIMDL_RT_AUTH query result: " + sessionFactory.getCurrentSession()?.createSQLQuery("SELECT * FROM AF.TIMDL_RT_AUTH").list()
    println "********* END QUERIES AGAINST OLD TABLE/DOMAIN TIMDL_RT_AUTH/IntermodalRatingAuth *********"
    println "----------------------END EXECUTING QUERIES ON TABLES------------------------------"
    return "Done with queries"
}
}

Домен создан с нуля (работает):

    class TFastCodes implements Serializable {

String applicationElement
String applicationCodeValue
String applicationCodeDescription
String casOwner
String accessTypeCode
static mapping = {
    table 'AF.TFAST_CODES'
    version false
    id composite: ['applicationElement', 'applicationCodeValue']
    applicationElement column:'APPL_ELE'
    applicationCodeValue column:'APPL_CD_VAL'
    applicationCodeDescription column:'APPL_CD_DESC'
    casOwner column:'CAS_OWNER'
    accessTypeCode column:'ACS_TYPE_CD'
}

static constraints = { applicationElement( blank:false)
                       applicationCodeValue( blank:false)}

}

Существующий домен (не работает):

class IntermodalRatingAuth {

String agreementNumber=LRAccrualConstants.STRING_EMPTY
Integer marketingRateKey= LRAccrualConstants.ZERO
String waybillRateAuthRef

static mapping = {
    table 'AF.TIMDL_RT_AUTH'
    version false
    agreementNumber column:'AGREEMENT_NUMB'
    marketingRateKey column:'MRKT_RATE_KEY'
    waybillRateAuthRef column:'WB_RT_AUTH_REF'
}
// Read-only. No constraints needed.
}

Консольный вывод:

----------------------START EXECUTING INSERTS ON TABLES------------------------------
********* START NEW TABLE/DOMAIN TFAST_CODES/TFastCodes *********
Valid TFastCodes domain? true
Hibernate: select tfastcodes_.APPL_ELE, tfastcodes_.APPL_CD_VAL, tfastcodes_.ACS_TYPE_CD as ACS_TYPE3_37_, tfastcodes_.APPL_CD_DESC as APPL_CD_4_37_, tfastcodes_.CAS_OWNER as CAS_OWNE5_37_ from AF.TFAST_CODES tfastcodes_ where tfastcodes_.APPL_ELE=? and tfastcodes_.APPL_CD_VAL=?
Hibernate: insert into AF.TFAST_CODES (ACS_TYPE_CD, APPL_CD_DESC, CAS_OWNER, APPL_ELE, APPL_CD_VAL) values (?, ?, ?, ?, ?)
********* END NEW TABLE/DOMAIN *********
********* START OLD TABLE/DOMAIN TIMDL_RT_AUTH/IntermodalRatingAuth *********
Valid IntermodalRatingAuth domain? true
********* END OLD TABLE/DOMAIN *********
----------------------END EXECUTING INSERTS ON TABLES------------------------------
----------------------START EXECUTING QUERIES ON TABLES------------------------------
********* START QUERIES AGAINST NEW TABLE/DOMAIN TFAST_CODES/TFastCodes *********
Hibernate: select this_.APPL_ELE as APPL_ELE1_37_0_, this_.APPL_CD_VAL as APPL_CD_2_37_0_, this_.ACS_TYPE_CD as ACS_TYPE3_37_0_, this_.APPL_CD_DESC as APPL_CD_4_37_0_, this_.CAS_OWNER as CAS_OWNE5_37_0_ from AF.TFAST_CODES this_
Gorm TFastCodes result 0: [applicationCodeDescription:ORPT-TEST      PR, applicationCodeValue:1, accessTypeCode:BU, applicationElement:NATIVE SQL2, casOwner:ZZZZ]
Gorm TFastCodes result 1: [applicationCodeValue:1, accessTypeCode:BU, applicationElement:GORM new 3, casOwner:ZZZZ, applicationCodeDescription:JUNK2]
Hibernate: SELECT * FROM AF.TFAST_CODES
Native TFAST_CODES query result: [[NATIVE SQL2, 1, BU, ORPT-TEST      PR, ZZZZ], [GORM new 3, 1, BU, JUNK2, ZZZZ]]
********* END QUERIES AGAINST NEW TABLE/DOMAIN TFAST_CODES/TFastCodes *********
********* START QUERIES AGAINST OLD TABLE/DOMAIN TIMDL_RT_AUTH/IntermodalRatingAuth *********
Gorm IntermodalRatingAuth result 0: [marketingRateKey:1, agreementNumber:GORM, waybillRateAuthRef:other string]
Hibernate: SELECT * FROM AF.TIMDL_RT_AUTH
Native TIMDL_RT_AUTH query result: [[1, NATIVE, 1, 2016-01-01-01.01.01.000000]]
********* END QUERIES AGAINST OLD TABLE/DOMAIN TIMDL_RT_AUTH/IntermodalRatingAuth *********
----------------------END EXECUTING QUERIES ON TABLES------------------------------

Дополнительная информация

В моем проекте Grails я установил БД в памяти для тестовой среды. Я хотел бы сохранять набор тестовых данных свежим каждый раз при выполнении определенных интеграционных тестов.

При сохранении данных с помощью GORM (domain.save ()) запросы GORM в службе могут легко находить данные, но не могут видеть данные, вставленные встроенными вставками SQL. С другой стороны, собственные SQL-запросы sessionFactory не могут видеть вставленные данные GORM, но могут видеть данные, вставленные собственным SQL.

Как вы можете видеть ниже, я выполняю flush: true и пробовал все известные мне команды для фиксации запуска данных. Большинство доменов, с которыми я тестировал, являются существующими доменами, и это никогда не сработает для них сразу. Однако, когда я создаю новые домены с нуля (используя команду Grails «создать домен»), это часто срабатывает. Мне кажется, что проблема где-то глубже в архитектуре Grails или Hibernate. Приведенные ниже примеры кода упрощены, но у меня есть сервис, который демонстрирует старый и новый домен бок о бок:

  • Оба вставляются как в GORM, так и в собственный SQL (две строки в каждой таблице)
  • Оба запрашиваются как GORM, так и собственным SQL
  • Старый домен показывает собственные вставленные данные для собственного запроса и вставленные данные GORM для запроса GORM.
  • В новом домене отображаются как результаты для собственного запроса, так и результаты для запроса GORM.

Если работает, то работает как для GORM, так и для родного. Если это не сработает, ни один из них не видит данные, вставленные другим.

Я предпринял шаги по отладке:

  1. Источник данных ниже намного больше того, с чего я начал. Мой первый был очень простым, без схемы AF, без дополнительных свойств и т. Д. Все было добавлено, чтобы попытаться устранить неполадки.
  2. Я попытался удалить сериализуемый, составной идентификатор и многое другое, что я мог найти, усложняло это.
  3. Между каждым запуском я выполняю команду grails clean-all и удаляю целевой каталог.
  4. У меня возникают проблемы с созданием согласованного сценария, который заставляет домен работать, но обычно это связано с комбинацией изменения класса, перемещения пакетов, изменения имени таблицы, определенного в домене, или создания совершенно нового домена / таблицы.

person Chris H    schedule 12.05.2016    source источник
comment
Вы пробовали использовать sql.commit() после sql.executeInsert(nativeInsert)?   -  person droggo    schedule 12.05.2016
comment
@droggo, спасибо за ваш комментарий. Я добавил заявление о фиксации, как вы сказали, и не увидел никаких изменений. Чтобы лучше проиллюстрировать, я также добавил второй домен, о котором упоминал ранее, где он работает для одного, а не для другого, и улучшил ведение журнала. Следует отметить две вещи: 1. Как видите, TFAST_CODES по-прежнему успешно находит оба оператора вставки для каждого запроса, но TIMDL_RT_AUTH (старый домен) по-прежнему находит только один. 2. Операторы журнала Hibernate отличаются для вставок и запросов к ним. Ключ к первопричине? Не уверен.   -  person Chris H    schedule 12.05.2016
comment
Некоторые мысли: вы можете попробовать обернуть свои вставки в одну транзакцию, чтобы сохранить его перед тем запросом. Вы также можете использовать новый сеанс для запроса и получить состояние вашей базы данных.   -  person    schedule 15.05.2016


Ответы (1)


Серджио / дрогго,

Спасибо за помощь! Думаю, я его выследил. Как это часто бывает, трудно точно знать, каким кодом поделиться, чтобы другие могли помочь в устранении неполадок. Похоже, что собственный SQL попал в конфигурацию источника данных, но домены GORM / были имитированы, поэтому не были сохранены в источнике данных. Я не осознавал этого, потому что использую плагин Grails под названием build-test-data, чья аннотация @Build является оболочкой для аннотации @Mock (очевидно, я не знал об этом). @Build предназначен только для модульных тестов, а не для интеграционных тестов. Чтобы кто-нибудь мог мне помочь, ему необходимо было увидеть аннотации интеграционного теста (см. Ниже). Приносим извинения за отсутствующий исходный код!

@TestFor(TestQueryService)
@Build([ContractFilter, ContractHeader, Qualifier, Provision, TFastCodes])
class ContractFilterServiceSpec extends Specification {
person Chris H    schedule 17.05.2016