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でこまめにレス付けてる所を見ると、気合の入っていることが分かります。