愛と勇気と缶ビール

ふしぎとぼくらはなにをしたらよいか

jOOQ (Java Object Oriented Querying) の使い方の紹介

jOOQ (http://www.jooq.org/) についてのメモ。 jOOQは、type safeかつdatabase orientedなクエリビルダーである。 database orientedなので、「アプリケーションからDBのことなんて全然意識したくないねん」的な思想に基いて作られた類のプロダクトとは異なり、思い通りのSQLを生成できるようになっている。

以下gradle前提で。version等はよしなに。

gradle

dependenciesはこんな感じ

compile 'org.jooq:jooq:3.7.1'
compile 'org.jooq:jooq-meta:3.7.1'

テーブル等に相当するクラスをgradle taskから生成するためにbuildscriptのdependenciesにも以下をかいておく

classpath 'org.jooq:jooq-codegen:3.7.1'

んで、以下のようなコードをbuild.gradleに書く。MySQL前提です。細かい部分は自分の環境に合わせてよしなに。

def genXml = { db, writer ->
    new groovy.xml.MarkupBuilder(writer).configuration('xmlns': 'http://www.jooq.org/xsd/jooq-codegen-3.6.0.xsd') {
        jdbc() {
            driver("com.mysql.jdbc.Driver")
            url("jdbc:mysql://localhost:3306/")
            user("root")
            password("")
        }
        generator() {
            database() {
                inputSchema(db)
            }
            generate() {
            }
            target() {
                packageName("com.foo.bar.jooq." + db)
                directory("src/main/java")
            }
        }
    }
}

def generateJooqSources = { db -> 
    def writer = new StringWriter()
    def xml = genXml(db, writer)
    org.jooq.util.GenerationTool.generate(
        javax.xml.bind.JAXB.unmarshal(new StringReader(writer.toString()), org.jooq.util.jaxb.Configuration.class)
    )
}

で、以下のようにデータベース毎にtaskを定義する、と。

task("jooq-generate-example") << {
    generateJooqSources(name)
}

若干DRYではないがここでは気にしない。

初期化

単にクエリを発行するだけなら、ConnectionやDataSourceからDSLContextクラスのオブジェクトを作ればよい。

DSLContext ctx = DSL.using(dataSource, SQLDialect.MYSQL);

クエリの書き方

SQLでやりたいことは大体出来る。たまにめんどくさい書き方をする必要があるが...

以下、userというtableからコード生成して、適宜static importしている前提。

単品SELECT

UserRecord record = ctx.selectFrom(USER)
    .where(USER.ID.equal(userId)
    .fetchOneInto(UserRecord.class); 

カラムを指定して単品SELECT

返り値を制御する関係で、結果として二回カラム名を指定する必要がある

String name = ctx.select(USER.NAME)
    .from(USER)
    .where(USER.ID.equal(userId)
    .fetchOne(USER.NAME); 

複数SELECT

List<UserRecord> userRecords = ctx.selectFrom(USER)
    .where(USER.NAME.equal(name)
    .fetchInto(UserRecord.class);

特定のカラムの値をkeyにしたMapを取得

PerlのDBIでいうところのselectrow_hashref的なことも出来る。1対1のマップ。

Map<Integer, UserRecord> userRecords = ctx.selectFrom(USER)
    .where(USER.NAME.equal(name)
    .fetchMap(USER.ID, UserRecord.class);

特定のカラムの値をkey、keyの値でグルーピングされたレコードのListをvalueにしたMapを取得

PerlのDBIでいうところの…なんだっけ。あったっけか。1対nのマップ。

Map<Integer, List<UserRecord>> userRecords = ctx.selectFrom(USER)
    .where(USER.ID.in(userIds))
    .fetchGroups(USER.STATUS, UserRecord.class);

INSERT

ctx.insertInto(USER, USER.NAME, USER.STATUS)
    .values(name, 0)
    .execute();

batch INSERT

List<UserRecord> userRecords = new ArrayList<UserRecord>();
...
ctx.batchInsert(userRecords)
    .execute();

UPDATE

ctx.update(USER)
    .set(USER.NAME, name)
    .where(USER.ID.equal(userId))
    .execute();

DELETE

ctx.deleteFrom(USER)
    .where(USER.ID.equal(userId))
    .execute();

トランザクション

ctx.transaction(new TransactionalRunnable() {
    @Override
    public void run(Configuration configuration) throws Exception {
        // ここの中
    }
})

where条件の動的な組み立て

SelectQuery<UserRecord> query = ctx.selectFrom(USER).getQuery();

for (User u : users) {
    Condition condition = USER.NAME.equal(userName).and(USER.STATUS.equal(1));
    query.addConditions(Operator.OR, condition);
}

List<UserRecord> userRecords = query.fetchInto(UserRecord.class);

DAO

使ったことはないが生成できるらしい

http://www.jooq.org/doc/2.6/manual/sql-execution/daos/

で、実際どうなん?

たまにかゆい所もあるけど、一通りのことは出来てまあまあ使いやすいです。

ドキュメントが結構充実しているのと、開発者がstackoverflowでこまめにレス付けてる所を見ると、気合の入っていることが分かります。

JDBCによるJavaデータベースプログラミング 第2版

JDBCによるJavaデータベースプログラミング 第2版