数据库第一是jOOQ设计的核心。jOOQ主要是为经典系统制作的,数据库一直在那里,而且一直在,永远不会离开。这是因为我们认为 "数据有质量"
数据是有质量的
好吧,它并不真的有什么重量。但是,在你的网络中甩动数据需要时间
想想看,这就像雕刻石头一样
你会把石头拖到你的工具上,还是把工具搬到石头上?
把你的代码移到数据上,可以明显加快#SQLpic.twitter.com/QDMS4D3jvH
- Chris Saxon(@ChrisRSaxon)2019年2月6日
这不仅转化为将逻辑移到离数据更近的地方(见我们之前关于JDBC往返的成本或生成供应商不可知的程序逻辑的文章),而且还避免了在迁移之间移动数据(例如RDBMS产品)。
与数据的 "沉重 "相比,应用程序和用户界面来去匆匆。说到go,也许你明天会把你所有的Java代码替换成一些go代码。但如果不是琐碎的,你会 保留数据库。
考虑到这一点,jOOQ假设你有一个预先存在的模式,你用Flyway或Liquibase来管理,然后你用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来生成代码,使用上述方法。
添加数据库变更管理
一个真实的例子是,在生成代码和/或运行集成测试之前,再次使用Flyway或Liquibase等对测试容器中的PostgreSQL实例进行完整的数据库迁移。
这只是使事情稍微复杂化,但不会产生任何不可能的问题。而不是用一个TC_INITSCRIPT ,你现在必须确保以下步骤在你的构建中连续执行。
- 一个数据库的testcontainers实例被启动
- 在该数据库中运行Flyway或Liquibase迁移。
- jOOQ代码生成器逆向工程该数据库
- 可以选择,你的集成测试也会重复使用该容器的数据库。
当然,你应该对你的代码进行集成测试。但就本讨论而言,这可能是一个可选的步骤,因为你可能对如何运行这些测试有不同的偏好,例如,更多的是全局性的,而不是只针对这个模块。但在我们的例子中,让我们包括测试。
你可以在这里找到使用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这里查看一个完整的例子,并玩一玩。