jOOQとは何か

2019年7月17日



jOOQまとめがあるので参照されたい

Java用のORマッパーを調査するうちにjOOQ(ジュークと読むらしい)というライブラリの存在を知った。

第一の機能としては、Javaプログラム内でSQLをより扱いやすくするものなのだが、ORマッパーとしての機能も充実しているようだ。

https://www.jooq.org/doc/3.11.11/manual-single-page/にマニュアルがある。

SQLをどう扱いやすくするのか?

Javaプログラム内でSQLを扱おうとすれば、通常は文字列として扱うほかはなく、「プログラム」にはならないのだが、jOOQを使うと「プログラム」としてSQLを構築することができる。

※もちろん、「定番」のORマッパとしてはHinernate等があるのだが、使ったことの無い人は絶対にやめた方が良い。経験者が心からおすすめするところだ。

マニュアルにある例としては、以下だ。

※BOOK.TITLE、BOOK、AUTHORなどの定義については後述。

String sql = create.select(BOOK.TITLE, AUTHOR.FIRST_NAME, AUTHOR.LAST_NAME)
                   .from(BOOK)
                   .join(AUTHOR)
                   .on(BOOK.AUTHOR_ID.eq(AUTHOR.ID))
                   .where(BOOK.PUBLISHED_IN.eq(1948))
                   .getSQL();

※これは、最終出力を「SQL文字列」とした例であり、このSQLを生のjava.sql.Connection等で実行することを前提としているが、もちろん、jOOQ内部で実行させることもできる。

ともあれ、SQLの構築をいわゆる「DSL」で行うことができるようになっている。

プログラム記述時点で不可能なSQLはありえなくなる

実際にやってみるとわかるのだが、このDSLはSQLの構造を反映したものになっており、語句の順序を変更すると、その時点でエラーになる。例えば以下だ。

String sql = create.select(BOOK.TITLE, AUTHOR.FIRST_NAME, AUTHOR.LAST_NAME)
                   .from(BOOK)
                   .where(BOOK.PUBLISHED_IN.eq(1948))
                   .join(AUTHOR) // ここでエラーが発生

だから、プログラム記述時点で正しいSQLになっているというわけだ。

データベースの方言を吸収

この方式にはもう一つのメリットがある。文字列でSQLで記述しているのでは無いため、つまり実際にDBに投入されるSQLを記述するのではないため、DBの方言に合わせたSQL生成が可能になる。例えば、

String sql = create.select(BOOK.TITLE, AUTHOR.FIRST_NAME, AUTHOR.LAST_NAME)
                   .from(BOOK)
                   .limit(10)
                   .getSQL();

と記述し、ターゲットとするデータベースを変更してみると、最終的に生成されるSQL文字列はMySQL、Oracle、Firebird等のDBの種類に応じて変更される。

※現在のjOOQは商用データベースを対象とするものについてはライセンス購入が必要であることに注意。オープンソース用のjOOQであれば無料で利用できる。

ただし、この機能を使わずとも、SQL文字列自体を解析して方言を変換する機能がある。これは後述する。

インライン、preparedの生成を切り替えられる

先の例で、以下の記述があるが、

                   .where(BOOK.PUBLISHED_IN.eq(1948))

SQLレベルでこのパラメータが投入される方法としてインラインかPreparedかを選択できる。前者の場合には、

where BOOK.PUBLISHED_IN = 1948

というSQLが生成され、後者の場合は、

where BOOK.PUBLISHED_IN = ?

というSQLとそのパラメータ値が生成される。

Preparedの場合は当然なのだが、インラインにおいて文字列が入力された場合には、適切にエスケープされるためSQLインジェクションの心配は無い。

テーブル名・フィールド名の扱いの冗長さと型安全

前項の例で、

String sql = create.select(BOOK.TITLE, AUTHOR.FIRST_NAME, AUTHOR.LAST_NAME)
                   .from(BOOK)

と記述されており、この記述をする以前に、BOOKやBOOK.TITLEの記述が必要なのだが、これはjOOQのジェネレータがデータベースを読み込んで適切なJavaソースを作成してくれるらしい。このJavaソースを利用して上のような記述を行うということだ。フィールド名、テーブル名等も含めて型安全にしたかったのだろう。

ただし、ジェネレータ機能を使わないこともできる。記述は以下のようになる。

import static org.jooq.impl.DSL.*;

。。。。

String sql = create.select(field("BOOK.TITLE"), field("AUTHOR.FIRST_NAME"), field("AUTHOR.LAST_NAME"))
                   .from(table("BOOK"))
                   .join(table("AUTHOR"))
                   .on(field("BOOK.AUTHOR_ID").eq(field("AUTHOR.ID")))
                   .where(field("BOOK.PUBLISHED_IN").eq(1948))
                   .getSQL();

jOOQでは、「型安全」が第一に考えられているので、テーブル名・フィールド名の「文字列」をそのまま引数にすることができない。いったん「テーブルという型」「フィールドという型」を作り出す必要がある。

そのためにこの例では、

import static org.jooq.impl.DSL.*;

。。。。

field("BOOK.TITLE")
table("BOOK")

などとして、文字列から、Field型、Table型を得ている。import static云々をつけて、単純にfield()、table()などと呼び出している。

ここが非常に面倒な点である。特にフィールドがそれぞれ「オブジェクト」となっているため、当然だが、単純に

select field_a, field_b, field_c, field_d,

などと文字列的に記述することはできない。

DBの方言変換機能

jOOQでは、先に示したように、各データベースの方言に合わせてSQL文字列を生成する機能があるのだが、逆に各データベースの方言有りのSQLを読み込み、それを解析し、別のDBの方言に変換してしまう機能もある。

この機能を使用したデモサイトがhttps://www.jooq.org/translate/だ。

ここで左側にMySQLとして

select * from test limit 10

を入力し、右側をFirebird2.5としてみると、

select *
from test
rows (0 + 1) to (0 + 10)

と変換されて表示される。

もちろん、この機能はjOOQライブラリ(3.9以上)中にある機能であり、説明としては、以下の5. Parsing Connectionの中にある。

ORマッパーとしての機能

ORマッパーとしての機能も非常に強力だ。

読み込み

読み込みは以下のような具合だ。Recordというのは、以下の例でのAuthor型のオブジェクトではなく、何にでも使用される型らしい。このオブジェクトに対してAUTHOR.FIRST_NAMEを指定して名称文字列を取得しなければならない。

Result<Record> result = create.select().from(AUTHOR).fetch();
for (Record r : result) {
    Integer id = r.getValue(AUTHOR.ID);
    String firstName = r.getValue(AUTHOR.FIRST_NAME);
    String lastName = r.getValue(AUTHOR.LAST_NAME);
    System.out.println("ID: " + id + " first name: " + firstName + " last name: " + lastName);
}

–>と思ったのだが、違っていた。実はジェネレータを使ってDBからJavaコード生成を行うと、そのテーブル用のJavaクラスが生成され、それを行読み込みで使うことができる。この件は後述する。詳細はjOOQのCRUD機能(ORマッピング)で述べる。

書き込み

後述することにする。

これも詳細は、jOOQのCRUD機能(ORマッピング)で述べる。