【问题标题】:jOOQ + Liquibase + H2 in unit test results in "schema not found" exception单元测试中的 jOOQ + Liquibase + H2 导致“找不到模式”异常
【发布时间】:2018-07-26 11:07:37
【问题描述】:

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

  • jOOQ 为 CRUD 操作生成的工件
  • Liquibase 处理架构演变

考虑到这么多,我正在尝试按如下方式设置测试:

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

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

  3. 使用上述连接,创建一个org.jooq.DSLContext,用于测试数据访问组件。

一个抽象类将这三个步骤封装在一个@Before 注释方法中,并且测试类扩展这个抽象类以利用初始化的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 以创建基于文件的数据库,我能够检查并验证架构确实存在。

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

【问题讨论】:

  • 也许将INIT=create schema if not exists foo 添加到您的连接字符串中,如下所示:"jdbc:h2:mem:foo;INIT=create schema if not exists foo\;MODE=MySQL;DB_CLOSE_DELAY=-1"
  • hmmm 但这不会创建一个空的数据库/模式吗?我需要使用Liquibase 生成的架构版本,以便jOOQ 生成的代码能够工作。
  • 什么“Liquibase”生成的架构版本?
  • @hsl 你是对的。再次阅读您的问题。可能是 jOOQ 中的一个问题,我对此一无所知。
  • @LukasEder 也许我之前的评论措辞不佳。我的意思是,在这个过程中,Liquibase 从更改日志中重新创建数据库/模式(这是模式中所有对象的一种清单)。重新创建该数据库/模式后,我想使用我的jOOQ 生成的代码与之交互。

标签: java sql h2 liquibase jooq


【解决方案1】:

经过多次尝试和错误,我能够通过解决两个问题来解决我的问题:

  1. Lukas 区分“数据库”(或“目录”)与“模式”的评论将我推向了正确的方向。尽管这些术语在 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 的不区分大小写的连接,toUpperCase() 调用仍然被证明是必要的。不知道为什么...

  1. jOOQ 引用所有模式对象的名称,以使其不区分大小写。但是,据我了解,引号用于在 H2 中强制区分大小写。因此,由于找不到对象,生成的查询中存在引号会导致大量错误。对此的补救措施是为查询生成器提供一个不同的RenderNameStyle,它会省略引号,a la:

    ...
    val settings = Settings().withRenderNameStyle(RenderNameStyle.AS_IS)
    
    val dslContext = DSL.using(connection, SQLDialect.H2, settings)
    ...
    

希望这可以对其他人有所帮助。

【讨论】:

  • 为了解决第 1 点,我有这个 JDBC URL jdbc:h2:mem:aad_data;DB_CLOSE_DELAY=-1;INIT=CREATE SCHEMA IF NOT EXISTS aas_data\\;SET SCHEMA aas_data。请注意创建模式和使用它的 INIT 部分。最终,我从生成的 SQL (Settings.withRenderSchema(false)) 中删除了架构,以便允许使用不同的架构名称。第 2 点是正确的!
猜你喜欢
  • 2016-09-05
  • 2013-02-21
  • 2020-08-18
  • 2017-04-06
  • 1970-01-01
  • 2021-10-24
  • 1970-01-01
  • 2021-06-14
  • 2011-06-21
相关资源
最近更新 更多