Skip to content

Instantly share code, notes, and snippets.

@stackunderflow111
Last active October 14, 2025 19:26
Show Gist options
  • Select an option

  • Save stackunderflow111/8b90e77d688ae64186bb4d23dfeb41ac to your computer and use it in GitHub Desktop.

Select an option

Save stackunderflow111/8b90e77d688ae64186bb4d23dfeb41ac to your computer and use it in GitHub Desktop.
Integrate Jooq with Testcontainers and Flyway
import nu.studer.gradle.jooq.JooqGenerate
import org.flywaydb.core.Flyway
import org.flywaydb.core.api.configuration.FluentConfiguration
import org.jooq.meta.jaxb.Configuration
import org.testcontainers.containers.JdbcDatabaseContainer
import org.testcontainers.containers.PostgreSQLContainer
import org.testcontainers.utility.DockerImageName
plugins {
id("nu.studer.jooq") version "7.1.1"
}
fun startContainer(imageName: String): JdbcDatabaseContainer<*> {
// for why we need the <Nothing> here, see
// https://kotlinlang.org/docs/whatsnew1530.html#improvements-to-type-inference-for-recursive-generic-types
// note that our current Gradle version uses an older version of Kotlin, and we have to use the "older" way
val container = PostgreSQLContainer<Nothing>(DockerImageName.parse(imageName))
container.start()
// register a buildFinished hook so that the container will be stopped when build finishes
// otherwise the container will only stop when the Gradle daemon stops
gradle.buildFinished {
container.stop()
}
return container
}
fun flywayMigrate(container: JdbcDatabaseContainer<*>, migrationFilesLocation: String) {
val configuration: FluentConfiguration = Flyway.configure()
.dataSource(container.jdbcUrl, container.username, container.password)
// the "filesystem:" prefix is required, otherwise flyway will treat "migrationFilesLocation" as classpath
.locations("filesystem:$migrationFilesLocation")
val flyway: Flyway = configuration.load()
flyway.migrate()
}
fun modifyJooqConfiguration(jooqGenerate: JooqGenerate, container: JdbcDatabaseContainer<*>) {
// this is the same as val jooqConfiguration = jooqGenerate.jooqConfiguration,
// but it's a private field, so I have to bypass the access restriction using reflection
val jooqConfigurationField = JooqGenerate::class.java.getDeclaredField("jooqConfiguration")
jooqConfigurationField.isAccessible = true
val jooqConfiguration = jooqConfigurationField.get(jooqGenerate) as Configuration
jooqConfiguration.jdbc.apply {
url = container.jdbcUrl
user = container.username
password = container.password
}
}
tasks.named<JooqGenerate>("generateJooq") {
val migrationFilesLocation = "location"
// Make the generateJooq task dependent on the migration scripts so we get proper build caching
// (trigger a clean rebuild only when the files change)
inputs.files(fileTree(migrationFilesLocation))
.withPropertyName("migrations")
.withPathSensitivity(PathSensitivity.RELATIVE)
// Let the task participate in incremental builds and build caching
allInputsDeclared.set(true)
outputs.cacheIf {
true
}
// starts the postgres container and runs the migration before generating Jooq files
doFirst {
val container = startContainer("postgres:13-alpine")
flywayMigrate(container, migrationFilesLocation)
modifyJooqConfiguration(this as JooqGenerate, container)
}
}
@Spirarel
Copy link

Don't suppose you've ever managed this with liquibase?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment