单元测试中的 jOOQ + Liquibase + H2 导致 "schema not found" 异常
jOOQ + Liquibase + H2 in unit test results in "schema not found" exception
我正在为一些数据访问代码编写单元测试。设置中的关键部分包括:
jOOQ
为 CRUD 操作生成了工件
Liquibase
处理架构演变
鉴于此,我尝试按如下方式设置测试:
创建一个 java.sql.Connection
以使用适当命名的模式初始化 H2 数据库。 (这里值得注意的是,连接是使用以下 URL 创建的:
jdbc:h2:mem:[schema-name];MODE=MySQL;DB_CLOSE_DELAY=-1
).
使用上述连接,通过在数据库架构中创建所有对象的更改日志调用 Liquibase
到 运行
使用上述连接,创建一个 org.jooq.DSLContext
可以用来测试数据访问组件。
一个抽象 class 将这三个步骤封装在一个 @Before
注释方法中,并且测试 class 扩展这个抽象 class 以利用初始化的 org.jooq.DSLContext
实例。像这样:
abstract class DbTestBase {
protected lateinit var dslContext: DSLContext
private lateinit var connection: Connection
open fun setUp() {
connection = DriverManager.getConnection("jdbc:h2:mem:foo;MODE=MySQL;DB_CLOSE_DELAY=-1")
// invoke Liquibase with this connection instance...
dslContext = DSL.using(connection, SQLDialect.H2)
}
open fun tearDown() {
dslContext.close()
connection.close()
}
}
class MyTest : DbTestBase() {
private lateinit var repository: Repository
@Before override fun setUp() {
super.setUp()
repository = Repository(dslContext)
}
@After override fun tearDown() {
super.tearDown()
}
@Test fun something() {
repository.add(Bar())
}
}
这会导致以下异常:
Caused by: org.h2.jdbc.JdbcSQLException: Schema "foo" not found; SQL statement:
insert into `foo`.`bar` (`id`) values (?) [90079-196]
at org.h2.message.DbException.getJdbcSQLException(DbException.java:345)
at org.h2.message.DbException.get(DbException.java:179)
at org.h2.message.DbException.get(DbException.java:155)
at org.h2.command.Parser.getSchema(Parser.java:688)
at org.h2.command.Parser.getSchema(Parser.java:694)
at org.h2.command.Parser.readTableOrView(Parser.java:5535)
at org.h2.command.Parser.readTableOrView(Parser.java:5529)
at org.h2.command.Parser.parseInsert(Parser.java:1062)
at org.h2.command.Parser.parsePrepared(Parser.java:417)
at org.h2.command.Parser.parse(Parser.java:321)
at org.h2.command.Parser.parse(Parser.java:293)
at org.h2.command.Parser.prepareCommand(Parser.java:258)
at org.h2.engine.Session.prepareLocal(Session.java:578)
at org.h2.engine.Session.prepareCommand(Session.java:519)
at org.h2.jdbc.JdbcConnection.prepareCommand(JdbcConnection.java:1204)
at org.h2.jdbc.JdbcPreparedStatement.<init>(JdbcPreparedStatement.java:73)
at org.h2.jdbc.JdbcConnection.prepareStatement(JdbcConnection.java:288)
at org.jooq.impl.ProviderEnabledConnection.prepareStatement(ProviderEnabledConnection.java:106)
at org.jooq.impl.SettingsEnabledConnection.prepareStatement(SettingsEnabledConnection.java:70)
at org.jooq.impl.AbstractQuery.prepare(AbstractQuery.java:410)
at org.jooq.impl.AbstractDMLQuery.prepare(AbstractDMLQuery.java:342)
at org.jooq.impl.AbstractQuery.execute(AbstractQuery.java:316)
... 25 more
我可以在重新生成架构的位置看到 Liquibase
日志记录。我已经更改了 H2 URL 以创建基于文件的数据库,我能够检查并验证该模式确实存在。
如果能帮助我发现方法中的任何错误,我将不胜感激。
经过多次试验和错误后,我通过解决两个问题解决了我的问题:
Lukas 区分 "database"(或 "catalog")与 "schema" 的评论将我推向了正确的方向。尽管这些术语在 MySQL(我的生产数据库)中似乎可以互换使用,但它们不在 H2 中。事后看来,这似乎很明显,但补救措施是调用 JDBC 调用来手动构建模式,然后在调用 Liquibase 重建模式之前将其设置为默认值,a la:
...
connection = DriverManager.getConnection(...)
connection.createStatement().executeUpdate("create schema $schemaName")
connection.schema = schemaName.toUpperCase()
// invoke Liquibase with this connection
...
尽管打开了到 H2 的 case-insensitive 连接,toUpperCase()
调用仍然被证明是必要的。不知道为什么...
jOOQ
引用所有模式对象的名称,使它们成为 case-insensitive。然而,据我了解,在 H2 中引号用于 enforce case-sensitivity。因此,生成的查询中存在引号会导致由于找不到对象而导致的大量错误。补救措施是向查询生成器提供一个不同的 RenderNameStyle
来省略引号,a la:
...
val settings = Settings().withRenderNameStyle(RenderNameStyle.AS_IS)
val dslContext = DSL.using(connection, SQLDialect.H2, settings)
...
希望这对其他人有帮助。
我正在为一些数据访问代码编写单元测试。设置中的关键部分包括:
jOOQ
为 CRUD 操作生成了工件Liquibase
处理架构演变
鉴于此,我尝试按如下方式设置测试:
创建一个
java.sql.Connection
以使用适当命名的模式初始化 H2 数据库。 (这里值得注意的是,连接是使用以下 URL 创建的:jdbc:h2:mem:[schema-name];MODE=MySQL;DB_CLOSE_DELAY=-1
).使用上述连接,通过在数据库架构中创建所有对象的更改日志调用
Liquibase
到 运行使用上述连接,创建一个
org.jooq.DSLContext
可以用来测试数据访问组件。
一个抽象 class 将这三个步骤封装在一个 @Before
注释方法中,并且测试 class 扩展这个抽象 class 以利用初始化的 org.jooq.DSLContext
实例。像这样:
abstract class DbTestBase {
protected lateinit var dslContext: DSLContext
private lateinit var connection: Connection
open fun setUp() {
connection = DriverManager.getConnection("jdbc:h2:mem:foo;MODE=MySQL;DB_CLOSE_DELAY=-1")
// invoke Liquibase with this connection instance...
dslContext = DSL.using(connection, SQLDialect.H2)
}
open fun tearDown() {
dslContext.close()
connection.close()
}
}
class MyTest : DbTestBase() {
private lateinit var repository: Repository
@Before override fun setUp() {
super.setUp()
repository = Repository(dslContext)
}
@After override fun tearDown() {
super.tearDown()
}
@Test fun something() {
repository.add(Bar())
}
}
这会导致以下异常:
Caused by: org.h2.jdbc.JdbcSQLException: Schema "foo" not found; SQL statement:
insert into `foo`.`bar` (`id`) values (?) [90079-196]
at org.h2.message.DbException.getJdbcSQLException(DbException.java:345)
at org.h2.message.DbException.get(DbException.java:179)
at org.h2.message.DbException.get(DbException.java:155)
at org.h2.command.Parser.getSchema(Parser.java:688)
at org.h2.command.Parser.getSchema(Parser.java:694)
at org.h2.command.Parser.readTableOrView(Parser.java:5535)
at org.h2.command.Parser.readTableOrView(Parser.java:5529)
at org.h2.command.Parser.parseInsert(Parser.java:1062)
at org.h2.command.Parser.parsePrepared(Parser.java:417)
at org.h2.command.Parser.parse(Parser.java:321)
at org.h2.command.Parser.parse(Parser.java:293)
at org.h2.command.Parser.prepareCommand(Parser.java:258)
at org.h2.engine.Session.prepareLocal(Session.java:578)
at org.h2.engine.Session.prepareCommand(Session.java:519)
at org.h2.jdbc.JdbcConnection.prepareCommand(JdbcConnection.java:1204)
at org.h2.jdbc.JdbcPreparedStatement.<init>(JdbcPreparedStatement.java:73)
at org.h2.jdbc.JdbcConnection.prepareStatement(JdbcConnection.java:288)
at org.jooq.impl.ProviderEnabledConnection.prepareStatement(ProviderEnabledConnection.java:106)
at org.jooq.impl.SettingsEnabledConnection.prepareStatement(SettingsEnabledConnection.java:70)
at org.jooq.impl.AbstractQuery.prepare(AbstractQuery.java:410)
at org.jooq.impl.AbstractDMLQuery.prepare(AbstractDMLQuery.java:342)
at org.jooq.impl.AbstractQuery.execute(AbstractQuery.java:316)
... 25 more
我可以在重新生成架构的位置看到 Liquibase
日志记录。我已经更改了 H2 URL 以创建基于文件的数据库,我能够检查并验证该模式确实存在。
如果能帮助我发现方法中的任何错误,我将不胜感激。
经过多次试验和错误后,我通过解决两个问题解决了我的问题:
Lukas 区分 "database"(或 "catalog")与 "schema" 的评论将我推向了正确的方向。尽管这些术语在 MySQL(我的生产数据库)中似乎可以互换使用,但它们不在 H2 中。事后看来,这似乎很明显,但补救措施是调用 JDBC 调用来手动构建模式,然后在调用 Liquibase 重建模式之前将其设置为默认值,a la:
... connection = DriverManager.getConnection(...) connection.createStatement().executeUpdate("create schema $schemaName") connection.schema = schemaName.toUpperCase() // invoke Liquibase with this connection ...
尽管打开了到 H2 的 case-insensitive 连接,toUpperCase()
调用仍然被证明是必要的。不知道为什么...
jOOQ
引用所有模式对象的名称,使它们成为 case-insensitive。然而,据我了解,在 H2 中引号用于 enforce case-sensitivity。因此,生成的查询中存在引号会导致由于找不到对象而导致的大量错误。补救措施是向查询生成器提供一个不同的RenderNameStyle
来省略引号,a la:... val settings = Settings().withRenderNameStyle(RenderNameStyle.AS_IS) val dslContext = DSL.using(connection, SQLDialect.H2, settings) ...
希望这对其他人有帮助。