経緯
Apache Wicket+Google Guiceで新しいWebアプリを作ることになり、DBのトランザクションをいい感じにしてくれるライブラリを探してみたら、"WicketでのSql2oの設定ってこれでいいの?"って感じの記事があった。
記事のコメントにもあるように、Sql2oにはConnectionPooling機能が無い。定番のdbcp?それともc3p0?Tomcat JDBC Poolなんてのもあったねーと検索してみたら、"Youたち!awfulなdbcpよりHikariCP使っちゃいなYo!"って感じのコメントがあった。
これまでにSql2oもHikariCPも利用経験がなかったので、Wicket上で動かしてみた。なお、log4jdbcからフォークされたlog4jdbc-log4j2も使ってみたかったので、これも組み合わせた。
手順
jarファイル/クラスパスはmavenなりダウンロードなりでプロジェクトに設定済みであることを前提とする。なおHikariCPの動作にはJavassist(本稿では3.18.1-GAを使用)も必要。
Propertiesファイルの作成
WicketはLoggerとしてslf4jを使っている。これをlog4jdbc-log4j2からも利用されるようにPropertiesファイルで設定する。log4jdbc-log4j2のホームページの"4.2. If you use SLF4J"を参考に、log4jdbc.log4j2.propertiesを作成すればよい。
log4jdbc.spylogdelegator.name=net.sf.log4jdbc.log.slf4j.Slf4jSpyLogDelegator
次に、HikariCPもPropertiesファイルで設定する1。log4jdbc-log4j2をJDBCのラッパとして指定するには、HikariCPのREADME.mdを参考に、jdbcUrl
で設定を行った方が簡単と思われる。
# 接続先はexample.com:5432で動作しているPostgreSQLのfooデータベースで、
# ユーザ名/パスワードはbaz/barと仮定
driverClassName=net.sf.log4jdbc.sql.jdbcapi.DriverSpy
jdbcUrl=jdbc:log4jdbc:postgresql://example.com:5432/foo
dataSource.user=baz
dataSource.password=bar
作成した2つのPropertiesファイルは、クラスディレクトリのルートに配置されるようにしておく(ソースディレクトリのルートにファイルを作っておけば良い)。
HikariCPのDataSourceを使用したSql2oインスタンスを生成
HikariCPから取得したDataSourceを食わせてSql2oインスタンスを作成すれば、どこからでもトランザクションを実行できる。
今回はせっかくWicketで試しているので、Wicket User Guiceの"Under the hood of the request processing"の"Storing arbitrary objects with metadata"の項の例を参考に、WebApplicationのサブクラスでMetaDataのMapにSql2oのインスタンスが格納されるようにした(Sql2oインスタンスはthread-safeなので、複数threadが共有できる)2。
ハマりどころ
- hikari.propertiesは自前で読み込む必要がある
- QuirksModeをDBMSにあわせて指定する必要がある
public class MyApplication extends WebApplication {
public static MetaDataKey<Sql2o> DB_KEY = new MetaDataKey<Sql2o>() {};
// -- snip --
@Override
protected void init() {
super.init();
initDBPooling();
// -- snip --
}
protected void initDBPooling() {
String configPath =
getClass().getClassLoader().getResource("").getPath() + "hikari.properties";
HikariConfig config = new HikariConfig(configPath);
HikariDataSource ds = new HikariDataSource(config);
Sql2o sql2o = new Sql2o(ds, new PostgresQuirks());
setMetaData(DB_KEY, sql2o);
}
public static Sql2o getDBPooling() {
return Application.get().getMetaData(DB_KEY);
}
// -- snip --
}
Sql2oの利用
WebApplicationのサブクラスのgetDBPooling()メソッドを使ってSql2oのインスタンスを取得し、トランザクション処理を開始する。
Sql2o sql2o = MyApplication.getDBPooling();
try (Connection con = sql2o.open()) {
// 書籍テーブルからISBNのリストを取得する想定
List<String> isbns = con.createQuery("select isbn from books")
.executeScalarList(String.class);
}
SQLの実行結果が正常にisbnsに入っていることを確かめれば動作確認完了。