Combining the power of Gradle, Flyway and JOOQ for seamless development with databases
Hi there,Ā in this post I will show you how easily you can combine the power of Gradle, with Flyway and JOOQ. At the end you willĀ have a recipeĀ for a build that automatically updates theĀ models whenever you update your database.
The problem
When developing applications with database access, we usually face the problem, that we need to adopt ourĀ code to changes on the database schema or vice versa.
As we try not to repeat things, aĀ great idea would be, that we can generated one of the both from the other. For example, generation database access and transfers objects from the actual database schema. As the database should be source of truth for schema data, the example looks like aĀ good way to go. But more on this later.
Another challenge is, the connection to the database for generating the classes from the schema. Generating the schema on the fly before the class generating kicks in, feels like an attractive idea. This would free the developerĀ fromĀ ensuring to have a database connection, when he regenerates the access classes. It also would be handy in the senseĀ that he can easily bootstrapĀ the environment on his local machine.
Letās see how we can proceed with this requirements / challenges.
The tooling
A solution for the second challenge, could beĀ Flyway, as itĀ is a smart tool forĀ managing and executingĀ schema upgrades for a lot of relational databases.Ā The development cycle with Flyway is (in very short) described as: Define the schema change in a SQL file, bootĀ your application and flyway will take care of it.
No we just need a generator from the schema data to data access / transfer objects. I find hereĀ JOOQĀ very handy, as itĀ is a library that providesĀ a wrapper forĀ relational database access via a powerful DSL where the SQL is visible in your code and not hiddenĀ behind some OR-mapper.
JOOQ comes along we the required code generator, so that we donāt have to write and change theĀ nasty and sometimes cumbersome code over and over again.
As we now have made the tech/library ā choices, letās proceed with the
Gradle integration
I will introduce the newĀ Gradle taskĀ generateJOOQ. ItĀ will take care of schema migration / creation with Flyway and code generation with JOOQ and should be executed before every compileJava run.
Letās start with the necessary configurations and sourceSets. The classes will be generated into the source setĀ generated which requires the following snippet to be available in the build file:
configurations {
compile.extendsFrom generatedCompile
}
sourceSets {
generated
main {
compileClasspath += generated.output
}
}The nextĀ step is nowĀ to make Gradle aware of both, Flyway and JOOQĀ by adding the necessary dependencies to the buildscript block of our build.gradle
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath 'org.jooq:jooq-codegen:3.6.1'
classpath 'com.h2database:h2:1.4.188'
classpath 'org.flywaydb:flyway-core:3.2.1'
}
}TheĀ H2 database is chosen for the sake of this example, but you can use any databaseĀ driver that can be used in conjunction with Flyway and JOOQ.
Now we must provide the configuration for JOOQ, so that it can generate the classes into the correct folder and classpath
def writer = new StringWriter()
new groovy.xml.MarkupBuilder(writer)
.configuration('xmlns': 'http://www.jooq.org/xsd/jooq-codegen-3.6.0.xsd') {
jdbc() {
driver('org.h2.Driver')
url("jdbc:h2:file:${project.projectDir}/build/generator/${project.name}")
user('sa')
password('')
}
generator() {
database() {
}
generate() {
}
target() {
packageName('com.coderskitchen.example')
directory('src/generated/java')
}
}
}As the next step,Ā we can create the generateJOOQ task:
import org.flywaydb.core.Flyway
task generateJOOQ() {
doLast {
def Flyway flyway = new Flyway();
flyway.setDataSource("jdbc:h2:file:${project.projectDir}/build/generator/${project.name}", "sa", null);
flyway.setLocations("filesystem:${project.projectDir}/src/main/resources/db/migration")
flyway.migrate();
org.jooq.util.GenerationTool.generate(
javax.xml.bind.JAXB.unmarshal(new StringReader(writer.toString()), org.jooq.util.jaxb.Configuration.class)
)
}
}As you can see the migration files are taken from src/main/resources/db/migration. This has the advantage that, if we like / have to, can deliver the migration SQL files within our app, so that we can migrate the database in the production / staging environment directly. This is very useful, if the environment is not under our control, for example, when the application runs on external customers machines.
TheĀ dependencies between the Gradle tasks will be compileJava ->Ā compileGeneratedJava -> generateJOOQ &&Ā clean:
compileGeneratedJava.dependsOn clean compileGeneratedJava.dependsOn generateJOOQ compileJava.dependsOn compileGeneratedJava
Finally we must add theĀ JOOQ dependencies to the dependencies toĀ the generatedCompile configuration, otherwise our project will not build:
dependencies {
generatedCompile 'org.jooq:jooq:3.6.1',
'org.jooq:jooq-meta:3.6.1',
'org.jooq:jooq-codegen:3.6.1'
}The whole minimal build file looks like this
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath 'org.jooq:jooq-codegen:3.6.1'
classpath 'com.h2database:h2:1.4.188'
classpath 'org.flywaydb:flyway-core:3.2.1'
}
}
import org.flywaydb.core.Flyway
group 'com.coderskitchen'
version '1.0.0'
apply plugin: 'java'
apply plugin: 'idea'
sourceCompatibility = 1.8
repositories {
mavenCentral()
}
configurations {
compile.extendsFrom generatedCompile
}
sourceSets {
generated
main {
compileClasspath += generated.output
}
}
dependencies {
generatedCompile 'org.jooq:jooq:3.6.1',
'org.jooq:jooq-meta:3.6.1',
'org.jooq:jooq-codegen:3.6.1'
testCompile group: 'junit', name: 'junit', version: '4.11'
}
def writer = new StringWriter()
new groovy.xml.MarkupBuilder(writer)
.configuration('xmlns': 'http://www.jooq.org/xsd/jooq-codegen-3.6.0.xsd') {
jdbc() {
driver('org.h2.Driver')
url("jdbc:h2:file:${project.projectDir}/build/generator/${project.name}")
user('sa')
password('')
}
generator() {
database() {
}
generate() {
}
target() {
packageName('com.coderskitchen.example')
directory('src/generated/java')
}
}
}
task generateJOOQ() {
doLast {
def Flyway flyway = new Flyway();
flyway.setDataSource("jdbc:h2:file:${project.projectDir}/build/generator/${project.name}", "sa", null);
flyway.setLocations("filesystem:${project.projectDir}/src/main/resources/db/migration")
flyway.migrate();
org.jooq.util.GenerationTool.generate(
javax.xml.bind.JAXB.unmarshal(new StringReader(writer.toString()), org.jooq.util.jaxb.Configuration.class)
)
}
}
compileGeneratedJava.dependsOn clean
compileGeneratedJava.dependsOn generateJOOQ
compileJava.dependsOn compileGeneratedJavaFinallyĀ we have all things together to try it out. Everything?Ā From the setup point of view, this is correct, just some example is missing.
The running example
The example is about a simple sailing race calendar,Ā written inĀ twoĀ iterations
- the first will just contain the table for the race dates with all the information about the place
- the second iteration willĀ migrate this schema to a more normalized one, where the place isĀ separated from the date
I published the example at github (https://github.com/coders-kitchen/gradle-jooq-flyway-example) and has two tags matching the mentioned iterations.
Bye andĀ have nice week
| Reference: | Combining the power of Gradle, Flyway and JOOQ for seamless development with databases from our JCG partner Peter Daum at the Coders Kitchen blog. |


