单元测试中的 jOOQ + Liquibase + H2 导致 "schema not found" 异常

jOOQ + Liquibase + H2 in unit test results in "schema not found" exception

我正在为一些数据访问代码编写单元测试。设置中的关键部分包括:

鉴于此,我尝试按如下方式设置测试:

  1. 创建一个 java.sql.Connection 以使用适当命名的模式初始化 H2 数据库。 (这里值得注意的是,连接是使用以下 URL 创建的: jdbc:h2:mem:[schema-name];MODE=MySQL;DB_CLOSE_DELAY=-1).

  2. 使用上述连接,通过在数据库架构中创建所有对象的更改日志调用 Liquibase 到 运行

  3. 使用上述连接,创建一个 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 以创建基于文件的数据库,我能够检查并验证该模式确实存在。

如果能帮助我发现方法中的任何错误,我将不胜感激。

经过多次试验和错误后,我通过解决两个问题解决了我的问题:

  1. 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() 调用仍然被证明是必要的。不知道为什么...

  1. 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)
    ...
    

希望这对其他人有帮助。