Grails 集成测试数据无法跨越原生 SQL 与 GORM 边界
Grails integration test data cannot cross native SQL versus GORM boundary
目标
当 运行ning 集成测试时,我想使用 GORM 使用预设数据填充数据库。
问题
对于某些域,本机 SQL 查询看不到 GORM 插入的数据,反之亦然(GORM 查询看不到本机 SQL 插入的数据)。在单个 运行 中,一个域会出现此问题,而另一个不会。
描述
请查看此 post 底部附近的控制台输出以获得清晰的描述。我认为可以在输出中看到解决方案的线索。
项目: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 插入。另一方面,sessionFactory 本机 SQL 查询无法看到插入的 GORM 数据,但能够看到本机 SQL.
插入的数据
正如您在下面看到的,我执行 flush:true 并尝试了我知道的每个命令来提交数据 运行。我测试过的大多数域都是现有域,它永远不会立即对它们起作用。但是,当我从头开始创建新域时(使用 Grails "create domain" 命令),它通常有效。在我看来,问题出在 Grails 或 Hibernate 架构的更深层次。下面的代码示例进行了简化,但我有一项服务可以并排演示旧域和新域:
- 两者都被 GORM 和本机插入 SQL(每个 table 两行)
- GORM 和本机都会查询两者 SQL
- 旧域显示本机查询的本机插入数据和 GORM 查询的 GORM 插入数据
- 新域同时显示本机查询的结果和 GORM 查询的两个结果
如果可行,则它适用于 GORM 和本机。如果它不起作用,则两者都看不到对方插入的数据。
我采取的调试步骤:
- 下面的数据源比我开始的要大得多。我的第一个非常基础,没有 AF 架构,没有其他属性等。添加所有内容以尝试解决问题。
- 我已经尝试删除可序列化、复合 ID 以及我能找到的几乎所有其他东西,这些都让事情变得更加复杂。
- 在每个 运行 之间,我执行 grails clean-all 命令并删除目标目录
- 我在创建使域开始工作的一致方案时遇到问题,但它通常与更改 class、移动包、更改 table 中定义的名称的组合有关域,或创建一个全新的 domain/table.
Sergio/droggo,
感谢您的帮助!我想我找到了。通常情况下,很难确切地知道要共享哪些代码以允许其他人帮助解决问题。似乎本机 SQL 正在访问数据源配置,但是 GORM/domains 是 模拟的 因此没有持久化到数据源。我没有意识到这一点,因为我正在使用一个名为 build-test-data 的 Grails 插件,它的 @Build 注释是 @Mock 注释的包装器(显然我不知道这一点)。 @Build 仅用于单元测试,不适用于集成测试。对于任何能够帮助我的人,他们都需要查看集成测试的注释(如下)。抱歉缺少源代码!
@TestFor(TestQueryService)
@Build([ContractFilter, ContractHeader, Qualifier, Provision, TFastCodes])
class ContractFilterServiceSpec extends Specification {
目标
当 运行ning 集成测试时,我想使用 GORM 使用预设数据填充数据库。
问题
对于某些域,本机 SQL 查询看不到 GORM 插入的数据,反之亦然(GORM 查询看不到本机 SQL 插入的数据)。在单个 运行 中,一个域会出现此问题,而另一个不会。
描述
请查看此 post 底部附近的控制台输出以获得清晰的描述。我认为可以在输出中看到解决方案的线索。
项目: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 插入。另一方面,sessionFactory 本机 SQL 查询无法看到插入的 GORM 数据,但能够看到本机 SQL.
插入的数据正如您在下面看到的,我执行 flush:true 并尝试了我知道的每个命令来提交数据 运行。我测试过的大多数域都是现有域,它永远不会立即对它们起作用。但是,当我从头开始创建新域时(使用 Grails "create domain" 命令),它通常有效。在我看来,问题出在 Grails 或 Hibernate 架构的更深层次。下面的代码示例进行了简化,但我有一项服务可以并排演示旧域和新域:
- 两者都被 GORM 和本机插入 SQL(每个 table 两行)
- GORM 和本机都会查询两者 SQL
- 旧域显示本机查询的本机插入数据和 GORM 查询的 GORM 插入数据
- 新域同时显示本机查询的结果和 GORM 查询的两个结果
如果可行,则它适用于 GORM 和本机。如果它不起作用,则两者都看不到对方插入的数据。
我采取的调试步骤:
- 下面的数据源比我开始的要大得多。我的第一个非常基础,没有 AF 架构,没有其他属性等。添加所有内容以尝试解决问题。
- 我已经尝试删除可序列化、复合 ID 以及我能找到的几乎所有其他东西,这些都让事情变得更加复杂。
- 在每个 运行 之间,我执行 grails clean-all 命令并删除目标目录
- 我在创建使域开始工作的一致方案时遇到问题,但它通常与更改 class、移动包、更改 table 中定义的名称的组合有关域,或创建一个全新的 domain/table.
Sergio/droggo,
感谢您的帮助!我想我找到了。通常情况下,很难确切地知道要共享哪些代码以允许其他人帮助解决问题。似乎本机 SQL 正在访问数据源配置,但是 GORM/domains 是 模拟的 因此没有持久化到数据源。我没有意识到这一点,因为我正在使用一个名为 build-test-data 的 Grails 插件,它的 @Build 注释是 @Mock 注释的包装器(显然我不知道这一点)。 @Build 仅用于单元测试,不适用于集成测试。对于任何能够帮助我的人,他们都需要查看集成测试的注释(如下)。抱歉缺少源代码!
@TestFor(TestQueryService)
@Build([ContractFilter, ContractHeader, Qualifier, Provision, TFastCodes])
class ContractFilterServiceSpec extends Specification {