https://github.com/benckx/liquibase-jooq-codegen-example
How to generate your DAO code from your Liquibase definition
https://github.com/benckx/liquibase-jooq-codegen-example
codegen codegenerator database example example-project gradle jooq jooq-generator kotlin liquibase sqlite3 sqllite tutorial tutorial-code
Last synced: 2 months ago
JSON representation
How to generate your DAO code from your Liquibase definition
- Host: GitHub
- URL: https://github.com/benckx/liquibase-jooq-codegen-example
- Owner: benckx
- Created: 2020-05-06T09:15:47.000Z (over 5 years ago)
- Default Branch: master
- Last Pushed: 2025-04-25T07:05:51.000Z (5 months ago)
- Last Synced: 2025-06-03T04:43:13.448Z (4 months ago)
- Topics: codegen, codegenerator, database, example, example-project, gradle, jooq, jooq-generator, kotlin, liquibase, sqlite3, sqllite, tutorial, tutorial-code
- Language: Kotlin
- Homepage:
- Size: 138 KB
- Stars: 2
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# About
The following tutorial explains how to generate DAO code based on a Liquibase definition. This generated code can be
used e.g. to insert a new row in a table:```kotlin
dslContext.transaction { cfg ->
val personDao = PersonDao(cfg)
val person = Person()
person.firstName = "Charles"
person.lastName = "Baudelaire"
personDao.insert(person)
}
```Classes `PersonDao` and `Person` have been generated during the Gradle build, directly from the Liquibase definition.
This reduces the boilerplate of writing DAO code and SQL queries in your application.# Technical stack
We will use SQLite for the sake of simplicity, but the same approach would work for other DB engines like MySQL or
Postgresql. SQLite is a light-weight DB engine which stores the entire database into a file - which is quite useful for
small systems like e.g. a podcasts manager app running on a phone, that must store information about what podcasts you
are subscribed to and which episodes you have already listened to.* JDK 21
* Kotlin
* SQLite
* Liquibase
* jOOQ# Code Generation
Create the Liquibase definition `liquibase-changelog.xml`:
```xml
```
In the Gradle build, add a task that runs before Kotlin compilation:
```groovy
tasks.register('dao-code-gen') {
doLast {
// code generation is configured inside this task
}
}compileKotlin.dependsOn(tasks."dao-code-gen")
```Inside this task, we create an H2 database:
```groovy
import java.sql.Connection
import java.sql.Statement// [...]
Connection conn = new Driver().connect("jdbc:h2:mem:test", null)
Statement stmt = conn.createStatement()
stmt.execute("drop all OBJECTS")
stmt.execute("create schema EXAMPLE_DB")
stmt.execute("set schema EXAMPLE_DB")
stmt.close()
```We then run the Liquibase on this database:
```groovy
import liquibase.Contexts
import liquibase.Liquibase
import liquibase.database.core.H2Database
import liquibase.database.jvm.JdbcConnection
import liquibase.resource.FileSystemResourceAccessor// [...]
def db = new H2Database()
db.setConnection(new JdbcConnection(conn))def liquibase = new Liquibase("src/main/resources/liquibase-changelog.xml", new FileSystemResourceAccessor(), db)
liquibase.update(new Contexts())
conn.commit()
```At this point, we have an H2 in-memory database containing our Liquibase definition (i.e. 1 table named `person`).
We then connect to this H2 database with jOOQ to generate the DAO code:
```groovy
import org.jooq.codegen.GenerationTool
import org.jooq.meta.jaxb.*// [...]
GenerationTool.generate(
new Configuration()
.withJdbc(new Jdbc()
.withDriver('org.h2.Driver')
.withUrl('jdbc:h2:mem:test')
.withUser('')
.withPassword(''))
.withGenerator(new Generator()
.withDatabase(
// exclude Liquibase-specific tables
new Database()
.withExcludes("DATABASECHANGELOG|DATABASECHANGELOGLOCK")
.withInputSchema("EXAMPLE_DB")
)
.withGenerate(new Generate()
.withPojos(true)
.withDaos(true))
.withTarget(
// specify the target package and directory
// by using the build folder, we ensure the generated code is removed on "clean"
// and is not versioned on Git
new Target()
.withPackageName('dev.encelade.example.dao.codegen')
.withDirectory("$buildDir/jooq"))
)
)
```Finally, we need to add this new generated folder as a source set, so Gradle knows to compile it along our application
code:```groovy
sourceSets {
main {
java {
srcDirs "$buildDir/jooq"
}
}
}
```Run the Gradle build with `./gradlew clean build`. The new folder will appear at `/build/jooq`.
# Use the generated DAO code
We first need some logic to create and access the SQLite database. If file `dbFileName` doesn't exist, it will be
created automatically. We will also run `updateLiquibase()` to apply any change made to the Liquibase definition
into the SQLite file.```kotlin
package dev.encelade.exampleimport liquibase.Contexts
import liquibase.Liquibase
import liquibase.database.DatabaseFactory
import liquibase.database.jvm.JdbcConnection
import liquibase.resource.ClassLoaderResourceAccessor
import org.jooq.DSLContext
import org.jooq.conf.Settings
import org.jooq.impl.DSL
import java.sql.Connection
import java.sql.DriverManagerobject DaoService {
fun getDslContext(dbFileName: String): DSLContext {
val conn = DriverManager.getConnection("jdbc:sqlite:$dbFileName")
updateLiquibase(conn)val settings = Settings().withRenderSchema(false)
return DSL.using(conn, settings)
}private fun updateLiquibase(conn: Connection) {
val db = DatabaseFactory.getInstance().findCorrectDatabaseImplementation(JdbcConnection(conn))
val liquibase = Liquibase("liquibase-changelog.xml", ClassLoaderResourceAccessor(), db)
liquibase.update(Contexts())
}}
```
The `DSLContext` is the jOOQ object you need to do any operation to the database. For example, we can use it to insert
a new entry in the table `person`:```kotlin
package dev.encelade.exampleimport dev.encelade.example.DaoService.getDslContext
import dev.encelade.example.dao.codegen.tables.daos.PersonDao
import dev.encelade.example.dao.codegen.tables.pojos.Person
import org.junit.jupiter.api.Testclass GenCodeTest {
private val dslContext = getDslContext("example.db")
private val readOnlyDao = PersonDao(dslContext.configuration())@Test
fun `the generated code can be used to insert records`() {
val before = readOnlyDao.count()val person = Person()
person.firstName = "Charles"
person.lastName = "Baudelaire"
insert(person)val after = readOnlyDao.count()
println("before insert: $before, after insert: $after")
assert(after == before + 1) { "Expected count to be ${before + 1}, but was $after" }
}private fun insert(person: Person) {
dslContext.transaction { cfg ->
val personDao = PersonDao(cfg)
personDao.insert(person)
}
}}
```When running the above, it should print the following (which increases by +1 every time):
```
before insert: 12, after insert: 13
```If you open `example.db` with a DB client, you can see the new entry:

If you later modify the Liquibase definition, for example by adding new tables, simply run `./gradlew clean build` to
re-generate the DAO code.# How To
To run it locally:
* `./gradlew clean build` to generate the jOOQ DAO code
* `GenCodeTest` contains an example of how to use the generated code# TODO
There are a few things I would still like to improve about this tutorial:
* Add date of birth to Person table
* Check if there is another generator implementation that can output Kotlin `data class` directly. Generated objects
with the builder pattern would also be interesting. Maybe I could add a generator myself.