{"id":21459502,"url":"https://github.com/benckx/liquibase-jooq-codegen-example","last_synced_at":"2026-02-22T22:38:11.415Z","repository":{"id":84322734,"uuid":"261708490","full_name":"benckx/liquibase-jooq-codegen-example","owner":"benckx","description":"How to generate your DAO code from your Liquibase definition","archived":false,"fork":false,"pushed_at":"2025-04-25T07:05:51.000Z","size":141,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-10-30T08:51:43.279Z","etag":null,"topics":["codegen","codegenerator","database","example","example-project","gradle","jooq","jooq-generator","kotlin","liquibase","sqlite3","sqllite","tutorial","tutorial-code"],"latest_commit_sha":null,"homepage":"","language":"Kotlin","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/benckx.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2020-05-06T09:15:47.000Z","updated_at":"2025-04-25T07:05:54.000Z","dependencies_parsed_at":null,"dependency_job_id":"7a9ec870-6d8e-41d2-88f8-e7f9cc31725d","html_url":"https://github.com/benckx/liquibase-jooq-codegen-example","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/benckx/liquibase-jooq-codegen-example","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/benckx%2Fliquibase-jooq-codegen-example","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/benckx%2Fliquibase-jooq-codegen-example/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/benckx%2Fliquibase-jooq-codegen-example/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/benckx%2Fliquibase-jooq-codegen-example/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/benckx","download_url":"https://codeload.github.com/benckx/liquibase-jooq-codegen-example/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/benckx%2Fliquibase-jooq-codegen-example/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29730142,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-22T20:09:16.275Z","status":"ssl_error","status_checked_at":"2026-02-22T20:09:13.750Z","response_time":110,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["codegen","codegenerator","database","example","example-project","gradle","jooq","jooq-generator","kotlin","liquibase","sqlite3","sqllite","tutorial","tutorial-code"],"created_at":"2024-11-23T06:29:27.279Z","updated_at":"2026-02-22T22:38:11.379Z","avatar_url":"https://github.com/benckx.png","language":"Kotlin","funding_links":["https://paypal.me/benckx/2"],"categories":[],"sub_categories":[],"readme":"\u003ca href=\"https://paypal.me/benckx/2\"\u003e\n\u003cimg src=\"https://img.shields.io/badge/Donate-PayPal-green.svg\"/\u003e\n\u003c/a\u003e\n\n# About\n\nThe following tutorial explains how to generate DAO code based on a Liquibase definition. This generated code can be\nused e.g. to insert a new row in a table:\n\n```kotlin\ndslContext.transaction { cfg -\u003e\n    val personDao = PersonDao(cfg)\n    val person = Person()\n    person.firstName = \"Charles\"\n    person.lastName = \"Baudelaire\"\n    personDao.insert(person)\n}\n```\n\nClasses `PersonDao` and `Person` have been generated during the Gradle build, directly from the Liquibase definition.\nThis reduces the boilerplate of writing DAO code and SQL queries in your application.\n\n# Technical stack\n\nWe will use SQLite for the sake of simplicity, but the same approach would work for other DB engines like MySQL or\nPostgresql. SQLite is a light-weight DB engine which stores the entire database into a file - which is quite useful for\nsmall systems like e.g. a podcasts manager app running on a phone, that must store information about what podcasts you\nare subscribed to and which episodes you have already listened to.\n\n* JDK 21\n* Kotlin\n* SQLite\n* Liquibase\n* jOOQ\n\n# Code Generation\n\nCreate the Liquibase definition `liquibase-changelog.xml`:\n\n```xml\n\n\u003cdatabaseChangeLog xmlns=\"http://www.liquibase.org/xml/ns/dbchangelog\"\n                   xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n                   xsi:schemaLocation=\"http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.4.xsd\"\u003e\n\n    \u003cchangeSet author=\"benckx\" id=\"0001\"\u003e\n        \u003ccreateTable tableName=\"person\"\u003e\n            \u003ccolumn name=\"id\" type=\"int\" autoIncrement=\"true\"\u003e\n                \u003cconstraints primaryKey=\"true\"/\u003e\n            \u003c/column\u003e\n            \u003ccolumn name=\"first_name\" type=\"varchar(255)\"/\u003e\n            \u003ccolumn name=\"last_name\" type=\"varchar(255)\"/\u003e\n        \u003c/createTable\u003e\n    \u003c/changeSet\u003e\n\u003c/databaseChangeLog\u003e\n```\n\nIn the Gradle build, add a task that runs before Kotlin compilation:\n\n```groovy\n\ntasks.register('dao-code-gen') {\n    doLast {\n        // code generation is configured inside this task\n    }\n}\n\ncompileKotlin.dependsOn(tasks.\"dao-code-gen\")\n```\n\nInside this task, we create an H2 database:\n\n```groovy\nimport java.sql.Connection\nimport java.sql.Statement\n\n// [...]\n\nConnection conn = new Driver().connect(\"jdbc:h2:mem:test\", null)\n\nStatement stmt = conn.createStatement()\nstmt.execute(\"drop all OBJECTS\")\nstmt.execute(\"create schema EXAMPLE_DB\")\nstmt.execute(\"set schema EXAMPLE_DB\")\nstmt.close()\n```\n\nWe then run the Liquibase on this database:\n\n```groovy\n\nimport liquibase.Contexts\nimport liquibase.Liquibase\nimport liquibase.database.core.H2Database\nimport liquibase.database.jvm.JdbcConnection\nimport liquibase.resource.FileSystemResourceAccessor\n\n// [...]\n\ndef db = new H2Database()\ndb.setConnection(new JdbcConnection(conn))\n\ndef liquibase = new Liquibase(\"src/main/resources/liquibase-changelog.xml\", new FileSystemResourceAccessor(), db)\nliquibase.update(new Contexts())\nconn.commit()\n```\n\nAt this point, we have an H2 in-memory database containing our Liquibase definition (i.e. 1 table named `person`).\n\nWe then connect to this H2 database with jOOQ to generate the DAO code:\n\n```groovy\nimport org.jooq.codegen.GenerationTool\nimport org.jooq.meta.jaxb.*\n\n// [...]\n\nGenerationTool.generate(\n        new Configuration()\n                .withJdbc(new Jdbc()\n                        .withDriver('org.h2.Driver')\n                        .withUrl('jdbc:h2:mem:test')\n                        .withUser('')\n                        .withPassword(''))\n                .withGenerator(new Generator()\n                        .withDatabase(\n                                // exclude Liquibase-specific tables\n                                new Database()\n                                        .withExcludes(\"DATABASECHANGELOG|DATABASECHANGELOGLOCK\")\n                                        .withInputSchema(\"EXAMPLE_DB\")\n                        )\n                        .withGenerate(new Generate()\n                                .withPojos(true)\n                                .withDaos(true))\n                        .withTarget(\n                                // specify the target package and directory\n                                // by using the build folder, we ensure the generated code is removed on \"clean\" \n                                // and is not versioned on Git\n                                new Target()\n                                        .withPackageName('dev.encelade.example.dao.codegen')\n                                        .withDirectory(\"$buildDir/jooq\"))\n                )\n)\n```\n\nFinally, we need to add this new generated folder as a source set, so Gradle knows to compile it along our application\ncode:\n\n```groovy\nsourceSets {\n    main {\n        java {\n            srcDirs \"$buildDir/jooq\"\n        }\n    }\n}\n```\n\nRun the Gradle build with `./gradlew clean build`. The new folder will appear at `/build/jooq`.\n\n# Use the generated DAO code\n\nWe first need some logic to create and access the SQLite database. If file `dbFileName` doesn't exist, it will be\ncreated automatically. We will also run `updateLiquibase()` to apply any change made to the Liquibase definition\ninto the SQLite file.\n\n```kotlin\npackage dev.encelade.example\n\nimport liquibase.Contexts\nimport liquibase.Liquibase\nimport liquibase.database.DatabaseFactory\nimport liquibase.database.jvm.JdbcConnection\nimport liquibase.resource.ClassLoaderResourceAccessor\nimport org.jooq.DSLContext\nimport org.jooq.conf.Settings\nimport org.jooq.impl.DSL\nimport java.sql.Connection\nimport java.sql.DriverManager\n\nobject DaoService {\n\n    fun getDslContext(dbFileName: String): DSLContext {\n        val conn = DriverManager.getConnection(\"jdbc:sqlite:$dbFileName\")\n        updateLiquibase(conn)\n\n        val settings = Settings().withRenderSchema(false)\n        return DSL.using(conn, settings)\n    }\n\n    private fun updateLiquibase(conn: Connection) {\n        val db = DatabaseFactory.getInstance().findCorrectDatabaseImplementation(JdbcConnection(conn))\n        val liquibase = Liquibase(\"liquibase-changelog.xml\", ClassLoaderResourceAccessor(), db)\n        liquibase.update(Contexts())\n    }\n\n}\n\n```\n\nThe `DSLContext` is the jOOQ object you need to do any operation to the database. For example, we can use it to insert\na new entry in the table `person`:\n\n```kotlin\npackage dev.encelade.example\n\nimport dev.encelade.example.DaoService.getDslContext\nimport dev.encelade.example.dao.codegen.tables.daos.PersonDao\nimport dev.encelade.example.dao.codegen.tables.pojos.Person\nimport org.junit.jupiter.api.Test\n\nclass GenCodeTest {\n\n    private val dslContext = getDslContext(\"example.db\")\n    private val readOnlyDao = PersonDao(dslContext.configuration())\n\n    @Test\n    fun `the generated code can be used to insert records`() {\n        val before = readOnlyDao.count()\n\n        val person = Person()\n        person.firstName = \"Charles\"\n        person.lastName = \"Baudelaire\"\n        insert(person)\n\n        val after = readOnlyDao.count()\n\n        println(\"before insert: $before, after insert: $after\")\n        assert(after == before + 1) { \"Expected count to be ${before + 1}, but was $after\" }\n    }\n\n    private fun insert(person: Person) {\n        dslContext.transaction { cfg -\u003e\n            val personDao = PersonDao(cfg)\n            personDao.insert(person)\n        }\n    }\n\n}\n```\n\nWhen running the above, it should print the following (which increases by +1 every time):\n\n```\nbefore insert: 12, after insert: 13\n```\n\nIf you open `example.db` with a DB client, you can see the new entry:\n\n![](/img/example.db.png)\n\nIf you later modify the Liquibase definition, for example by adding new tables, simply run `./gradlew clean build` to\nre-generate the DAO code.\n\n# How To\n\nTo run it locally:\n\n* `./gradlew clean build` to generate the jOOQ DAO code\n* `GenCodeTest` contains an example of how to use the generated code\n\n# TODO\n\nThere are a few things I would still like to improve about this tutorial:\n\n* Add date of birth to Person table\n* Check if there is another generator implementation that can output Kotlin `data class` directly. Generated objects\n  with the builder pattern would also be interesting. Maybe I could add a generator myself.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbenckx%2Fliquibase-jooq-codegen-example","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbenckx%2Fliquibase-jooq-codegen-example","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbenckx%2Fliquibase-jooq-codegen-example/lists"}