2018年12月のJJUG-CCC(日本Javaユーザーズグループクロスコミュニティカンファレンス)でツール比較しながら語る O/RマッパーとDBマイグレーションの実際のところというタイトルで登壇させていただきました。
数あるO/RマッパーやDBマイグレーションツールの中でも、jOOQ推し、Flyway推しという内容でした。一方で巷のブログや人づてに聞く話ではありますが、それらの使い方を若干間違えているケースが多々あるようです。 特にまずいのが、jOOQが自動生成したJavaソースコードをgitのコミット対象としてバージョン管理しているケースがそこそこ観測されることです。それはやめたほうがいいです。 複数の開発者がうっかり同時期に同じテーブルに対してカラム追加等をすると、jOOQで自動生成した大量のJavaコード群は必ずコンフリクトを起こしてしまうからです。
jOOQは、指定したDBスキーマの構造を正確無比に読み取って、O/RマッピングをラクにするJavaコードを自動生成するのがキモです。 ということは、正確無比にバージョン管理しておくべきなのはDBスキーマ定義つまりCREATE TABLE, ALTER TABLEなどいわゆるDDL文のほうです。そこを実現するのがFlywayです。
jOOQが自動生成するのはJavaコードですので、つい src/main/java の配下を出力先として指定したくなりますが、そこが落とし穴です。gradleを使っている場合は build/の配下をjOOQの自動生成コードの出力先として指定したうえで、ソースコードの在り処も指定することで、コンパイルは通ります。IntelliJ IDEA上でも問題はありません。ただしEclipseという古代兵器のことは存じません。
git, JDK8以上, dockerが入ったPCを用意して、下記のコマンドを実行するだけです。
git clone git@github.com:nabedge/jooq-flyway-spboot-sample.git
cd jooq-flyway-spboot-sample
sh setup.sh
sh ./gradlew run -p pj-web
ブラウザで http://localhost:8080 を開く
このあたりから下だけでももう少し解説しましょう。
jooq {
version = "${jooqVersion}"
edition = 'OSS' // if you use oracle, you should pay :-)
// the name "sample" -> task name "generateSampleJooqSchemaSource" . see below.
sample (sourceSets.main) {
jdbc {
driver = "${jdbcDriver}"
url = "${dbUrl}"
user = "${dbUser}"
password = "${dbPassword}"
}
generator {
target {
packageName = "${jooqDestPackage}"
directory = "${jooqDestDir}"
}
strategy {
name = 'com.example.db.jooq.generator.SamplePrefixGeneratorStrategy'
}
database() {
name = 'org.jooq.meta.postgres.PostgresDatabase'
inputSchema = "public"
}
generate() {
daos = true
immutablePojos = true
pojosEqualsAndHashCode = true
}
}
}
}
compileJava {
dependsOn generateSampleJooqSchemaSource
sourceSets.main.java.srcDirs(jooqDestDir)
}
ツール比較しながら語る O/RマッパーとDBマイグレーションの実際のところ の50ページ付近で話したとおり、デフォルトではjOOQはテーブル名とまったく同じ名前のJavaクラスを作ってしまいます。 カスタムを強く推奨します。 クラス名の衝突はコーディング作業中のけっこうなストレスにつながります。
そこで、上のbuild.gradleのこの部分
strategy {
name = 'com.example.db.jooq.generator.SamplePrefixGeneratorStrategy'
}
が重要になってきます。このクラスは pj-db-custom-strategyというサブプロジェクトのたった一つのクラスとして実装されています。
import org.jooq.codegen.DefaultGeneratorStrategy;
import org.jooq.meta.Definition;
public class SamplePrefixGeneratorStrategy extends DefaultGeneratorStrategy {
@Override
public String getJavaClassName(final Definition definition, final Mode mode) {
String name = super.getJavaClassName(definition, mode);
switch (mode) {
case POJO:
return name + "Vo";
case DEFAULT:
return 'J' + name;
}
return name;
}
見ての通り、jOOQが自動生成するJavaクラスのプレフィクスとして全て”J”がつくようになっています。これによって、実際のDBアクセスのコードでは
final JBook jBook = JBook.BOOK;
final List<BookVo> selected = dslContext
.select(
jBook.ISBN,
jBook.TITLE,
jBook.PUBLISH_DATE
)
.from(jBook)
.orderBy(jBook.PUBLISH_DATE)
.fetchInto(BookVo.class);
// .fetchInto(Book.class); // or you can use original class directly !
return selected
.stream()
.map(bookVo -> {
Book book = new Book();
book.setIsbn(bookVo.getIsbn());
book.setTitle(bookVo.getTitle());
book.setPublishDate(bookVo.getPublishDate().toLocalDate());
return book;
})
.collect(Collectors.toList());
このように、
これらの名前衝突を避けることができるようになります。
それぞれ読みながらサンプルを動かしてIDEにインポートしてみれば、勘所がおわかりいただけるかと思います。
Happy Hacking !