使用Testcontainers生成jOOQ代码的详细指南

742 阅读5分钟

数据库第一是jOOQ设计的核心。jOOQ主要是为经典系统制作的,数据库一直在那里,而且一直在,永远不会离开。这是因为我们认为 "数据有质量"

数据是有质量的

好吧,它并不真的有什么重量。但是,在你的网络中甩动数据需要时间

想想看,这就像雕刻石头一样

你会把石头拖到你的工具上,还是把工具搬到石头上?

把你的代码移到数据上,可以明显加快#SQLpic.twitter.com/QDMS4D3jvH

- Chris Saxon(@ChrisRSaxon)2019年2月6

这不仅转化为将逻辑移到离数据更近的地方(见我们之前关于JDBC往返的成本生成供应商不可知的程序逻辑文章),而且还避免了在迁移之间移动数据(例如RDBMS产品)。

与数据的 "沉重 "相比,应用程序和用户界面来去匆匆。说到go,也许你明天会把你所有的Java代码替换成一些go代码。但如果不是琐碎的,你 保留数据库。

考虑到这一点,jOOQ假设你有一个预先存在的模式,你用FlywayLiquibase来管理,然后你用jOOQ用代码生成器来反向设计你的更新模式

过去的日子

在过去,建立一个Oracle实例是非常繁重的,而且也很困难。我记得在一家公司工作时,我们有共享的开发和测试实例。模式总是在不断变化。我们无法假设一个稳定的开发版本。

因此,将jOOQ代码生成器指向一个活的数据库曾经是一个小小的挑战,这就是为什么jOOQ提供了替代的、无连接的代码生成模式,包括:

  • [JPADatabase](https://www.jooq.org/doc/latest/manual/code-generation/codegen-jpa/)如果你有一个预先存在的基于JPA实体的元模型。
  • 的。 [XMLDatabase](https://www.jooq.org/doc/latest/manual/code-generation/codegen-xml/),如果你有某种形式的XML版本的模式,你可以将其XSL转换为jOOQ的格式。
  • 架构 [DDLDatabase](https://www.jooq.org/doc/latest/manual/code-generation/codegen-ddl/),它可以解释你的DDL脚本,例如,你传递给Flyway的,或者由pg_dump产生的。
  • 脚本 [LiquibaseDatabase](https://www.jooq.org/doc/latest/manual/code-generation/codegen-liquibase/),它模拟Liquibase数据库迁移,并使用模拟的数据库输出作为代码生成器的元信息来源。

但上述所有的方式都有相同的局限性。你不能真正使用许多供应商的特定功能,如高级存储程序、数据类型等。

使用testcontainers的现代方法

理想情况下,除非你支持几个RDBMS产品(大多数人不支持),否则你应该只使用你的生产数据库产品,例如PostgreSQL。

感谢testcontainers.org,在Docker容器中以编程方式或配置方式启动任何版本的PostgreSQL实例非常容易。如果你有一个包含数据库的SQL脚本,你可以把它提供给testcontainers的JDBC URL,例如,像这样:

jdbc:tc:postgresql:13:///sakila?TC_TMPFS=/testtmpfs:rw&TC_INITSCRIPT=file:${basedir}/src/main/resources/postgres-sakila-schema.sql

更多信息,请看他们关于JDBC支持的文档。现在,在你的项目classpath上添加testcontainers依赖项,例如:

<dependency>
  <groupId>org.testcontainers</groupId>
  <artifactId>postgresql</artifactId>
</dependency>

并在jOOQ的代码生成配置中使用ContainerDatabaseDriver ,而不是实际的PostgreSQL驱动,例如使用Maven时:

<plugin>
  <groupId>org.jooq</groupId>
  <artifactId>jooq-codegen-maven</artifactId>

  <executions>
    <execution>
      <id>java-generator</id>
      <phase>generate-sources</phase>
      <goals>
        <goal>generate</goal>
      </goals>

      <configuration>
        <jdbc>
          <driver>
            org.testcontainers.jdbc.ContainerDatabaseDriver
          </driver>
          <url>${db.url}</url>
          <user>${db.username}</user>
          <password>${db.password}</password>
        </jdbc>
        <generator>
          <database>
            <inputSchema>public</inputSchema>
          </database>
          <target>
            <packageName>org.jooq.example.tc.db</packageName>
            <directory>src/main/java</directory>
          </target>
        </generator>
      </configuration>
    </execution>
  </executions>

  <dependencies>

    <!-- Junit seems a transitive dependency of testcontainers? -->
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.13.1</version>
    </dependency>
  </dependencies>
</plugin>

就这么简单!请看 [jOOQ-testcontainers-example](https://github.com/jOOQ/jOOQ/tree/main/jOOQ-examples/jOOQ-testcontainers-example)以了解一个可运行的例子,该例子使用testcontainers来生成代码,使用上述方法。

添加数据库变更管理

一个真实的例子是,在生成代码和/或运行集成测试之前,再次使用FlywayLiquibase等对测试容器中的PostgreSQL实例进行完整的数据库迁移。

这只是使事情稍微复杂化,但不会产生任何不可能的问题。而不是用一个TC_INITSCRIPT ,你现在必须确保以下步骤在你的构建中连续执行。

  1. 一个数据库的testcontainers实例被启动
  2. 在该数据库中运行Flyway或Liquibase迁移。
  3. jOOQ代码生成器逆向工程该数据库
  4. 可以选择,你的集成测试也会重复使用该容器的数据库。

当然,你应该对你的代码进行集成测试。但就本讨论而言,这可能是一个可选的步骤,因为你可能对如何运行这些测试有不同的偏好,例如,更多的是全局性的,而不是只针对这个模块。但在我们的例子中,让我们包括测试。

你可以在这里找到使用testcontainers/flyway/jOOQ的完整例子

启动testcontainers实例

不幸的是,testcontainers还没有提供任何Maven/Gradle插件来在构建期间调用容器生命周期管理。我已经在这里创建了一个功能请求,你应该支持它:https://github.com/testcontainers/testcontainers-java/issues/4397。

但我们可以通过使用强大的Maven避风港--groovy-maven-plugin (gradle的思路是一样的),轻松地帮助自己:

<plugin>
  <groupId>org.codehaus.gmaven</groupId>
  <artifactId>groovy-maven-plugin</artifactId>
  <version>2.1.1</version>
  <executions>
    <execution>
      <!-- Start the container in any phase before the actual code
           generation is required, i.e. at the latest in
           generate-sources -->
      <phase>generate-sources</phase>
      <goals>
        <goal>execute</goal>
      </goals>
      <configuration>
        <source>
          db = new org.testcontainers.containers.PostgreSQLContainer(
                  "postgres:latest")
            .withUsername("${db.username}")
            .withDatabaseName("postgres")
            .withPassword("${db.password}");
            
          db.start();

          // After you've started the container, collect its generated
          // JDBC URL (which contains a random port)
          project.properties.setProperty('db.url', db.getJdbcUrl());
        </source>
      </configuration>
    </execution>
  </executions>
  
  <dependencies>
    <dependency>
      <groupId>org.testcontainers</groupId>
      <artifactId>postgresql</artifactId>
      <version>1.15.2</version>
    </dependency>
  </dependencies>
</plugin>

所以,这就启动了一个容器,并让它一直运行到构建结束。我不会展示优雅关闭,因为这个例子不需要,但你当然也可以实现这个。

现在,迁移你的数据库

上面的数据库是空的。现在运行迁移,在例子中使用Flyway,但用Liquibase也是一样的:

<plugin>
  <groupId>org.flywaydb</groupId>
  <artifactId>flyway-maven-plugin</artifactId>
  <version>7.14.0</version>
  <executions>
    <execution>

      <!-- We run the migration in the same phase, before jOOQ's
           code generation -->
      <phase>generate-sources</phase>
      <goals>
        <goal>migrate</goal>
      </goals>
      <configuration>

        <!-- This URL has been set by groovy, above -->
        <url>${db.url}</url>
        <user>${db.username}</user>
        <password>${db.password}</password>
        <locations>
          <location>
            filesystem:src/main/resources/db/migration
          </location>
        </locations>
      </configuration>
    </execution>
  </executions>
</plugin>

如果你愿意的话,可以在这个配置中添加你的迁移的所有额外的复杂性,jOOQ不会知道任何事情。

现在,生成代码

同样,这里没有什么特别的:

<plugin>
  <groupId>org.jooq</groupId>
  <artifactId>jooq-codegen-maven</artifactId>

  <executions>
    <execution>
      <id>java-generator</id>

      <!-- Same phase as above, but the previous plugins have already
           executed, so we're generating the db post migration -->
      <phase>generate-sources</phase>
      <goals>
        <goal>generate</goal>
      </goals>

      <configuration>
        <jdbc>

          <!-- Again, this URL has been set by groovy, above -->
          <url>${db.url}</url>
          <user>${db.username}</user>
          <password>${db.password}</password>
        </jdbc>
        <generator>
          <database>
            <inputSchema>public</inputSchema>
          </database>
          <target>
            <packageName>org.jooq.example.db</packageName>
          </target>
        </generator>
      </configuration>
    </execution>
  </executions>
</plugin>

最后,可以选择进行集成测试

如果你想在你的集成测试中也重新使用上述带有迁移数据库的容器,你可以像下面这样把生成的JDBC URL传递给maven-surefire-plugin

<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-surefire-plugin</artifactId>
  <configuration>
    <systemPropertyVariables>

      <!-- Again, this URL has been set by groovy, above -->
      <db.url>${db.url}</db.url>
      <db.username>${db.username}</db.username>
      <db.password>${db.password}</db.password>
    </systemPropertyVariables>
  </configuration>
</plugin>

有很多方法可以实现同样的事情,这是其中的一种,开箱即用,效果很好。你可以从github这里查看一个完整的例子,并玩一玩。

github.com/jOOQ/jOOQ/t…