JOOQ使い始め(gradle-jooq-pluginによるコード生成)

JPAが辛くなってきたので、JOOQへの乗り換えを検討中。

Why JOOQ?

MyBatisの方が手堅い気がしますが、JOOQを選んだ理由としては、以下のものです。

  • タイプセーフ
  • Annotation Processingではなく、プラグインによるコード生成
    • 今後のJavaのリリースサイクルを考えると、こちらの方が安心…?
      • JOOQはJOOQで1文字アンダースコア識別子( _ )のIssueが上がっていたりするけど…。
  • Spring Boot Starterでサポートされていたり、開発も活発なので、発展性がある&しばらくはなくならない気がする

本記事では、「JOOQって何?」という説明は割愛して、新規プロジェクトでも既存のプロジェクトでも利用できるようなポイントをメモしておきます。

前提条件

DBのスキーマはFlywayで管理されているものとします。ただし、JOOQとFlywayは直接関係はないので、他のマイグレーションツールでも多分やり方は同じはずです。

  • Java 11
  • Gradle 5.1
    • Java 11に対応しているのが5.0からであるため、5.0以上が必須です。
  • Spring Boot 2.1.1.RELEASE
  • JOOQ: 3.11.7
    • Spring Boot の依存関係管理で解決されるため、明示的に指定はしていません。

手順

build.gradle

buildscript {
    ext {
        springBootVersion = '2.1.1.RELEASE'
    }
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
        classpath('org.glassfish.jaxb:jaxb-runtime:2.3.1') // (1)
    }
}

plugins {
    id 'org.flywaydb.flyway' version '5.2.4'
    id 'nu.studer.jooq' version '3.0.2'           // (2)
}

apply plugin: 'java'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'

group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = 11

repositories {
    mavenCentral()
}

dependencies {
    // 略
    implementation('org.springframework.boot:spring-boot-starter-jooq') // (3)
    // 略
    jooqRuntime('org.glassfish.jaxb:jaxb-runtime') // (1)
    jooqRuntime('javax.activation:javax.activation-api') // (1)
}

flyway {
    url = 'jdbc:mysql://127.0.0.1:3306/testdb'
    user = 'dbuser'
    password = 'dbpass'
}

jooq {  // (4)
    main(sourceSets.main) {
        jdbc {
            url = 'jdbc:mysql://127.0.0.1:3306/testdb'
            user = 'dbuser'
            password = 'dbpass'
        }
        generator {
            name = 'org.jooq.codegen.DefaultGenerator'
            database {
                name = 'org.jooq.meta.mysql.MySQLDatabase'
                includes = '.*'
                excludes = 'flyway_schema_history' // (5)
                inputSchema = 'testdb'
            }
            strategy {
                name = 'org.jooq.codegen.DefaultGeneratorStrategy'
            }
            generate {
            }
            target { // (6)
                packageName = 'com.example.demo.jooq'
                directory = 'src/main/java'
            }
        }
    }
}

project.tasks.getByName('compileJava').dependsOn -= 'generateMainJooqSchemaSource' // (7)
説明
(1) Java 11からはJAXBが外されてしまうため、必要な依存関係を明示的に追加します。
(2) Gradleプラグインとしてgradle-jooq-pluginを適用します。
JOOQのコード生成を行う方法はいくつかありますが、Gradleタスクとして実行できると簡単かつ便利なので、これを使います。
(3) アプリケーションからJOOQを使うための依存を追加。
(4) JOOQコード生成のための設定を記述します。jooq-codegenXMLでの設定の替わりになるものです。
(5) Flywayがスキーマ管理を行うために作成するテーブルは、アプリケーションから使うことはないので、コード生成から除外します。
(6) 自動生成されるコードの出力先です。
(7) デフォルトでは、compileJavaタスクが実行される前にコード生成も実行されるのですが、これを除外します。
こちらは有効化するかどうかはお好みで。

タスク実行

$ ./gradlew generateMainJooqSchemaSource

開発の進め方

  1. Flywayでスキーマ変更を実施
    1. DDL(SQL)を作る
    2. ./gradlew flywayMigrate を実行
  2. generateMainJooqSchemaSource タスクを実行し、コミット

上記の1.と2.で一つのfeature(プルリクエスト)になっているのが分かりやすく、トラブルが起きにくい気がします。

また、自動生成されたコードは、直接編集しないよう、開発チームで徹底しておく必要がありそうです。手動で変更してしまうと次にコード生成された時に上書きされてしまうので。

(追記)自動生成されたコードをコミットするべきか?

並行開発の妨げになるので「自動生成されたコードはコミット対象外!」という主張もあり、なるほどと思いました。

ツール比較しながら語る O/RマッパーとDBマイグレーションの実際のところ

プロジェクトの規模が大きく、スキーマ変更が頻繁に発生する場合は、提言されているとおり、コミット対象外とした方がよいのかもしれません。