HikariCP + Sql2o + log4jdbc-log4j2 + Wicket

## 経緯
[Apache Wicket](http://wicket.apache.org/)+Google Guiceで新しいWebアプリを作ることになり、DBのトランザクションをいい感じにしてくれるライブラリを探してみたら、["WicketでのSql2oの設定ってこれでいいの？"](http://stackoverflow.com/questions/21286040/apache-wicket-application-global-settings-for-database-connection)って感じの記事があった。

記事のコメントにもあるように、Sql2oにはConnectionPooling機能が無い。定番のdbcp？それともc3p0？Tomcat JDBC Poolなんてのもあったねーと検索してみたら、["Youたち!awfulなdbcpよりHikariCP使っちゃいなYo!"](https://groups.google.com/forum/#!msg/sql2o/qhoSG2u2NWc/mwawcVeYZt4J)って感じのコメントがあった。

これまでに[Sql2o](http://www.sql2o.org/)も[HikariCP](http://brettwooldridge.github.io/HikariCP/)も利用経験がなかったので、Wicket上で動かしてみた。なお、log4jdbcからフォークされた[log4jdbc-log4j2](https://code.google.com/p/log4jdbc-log4j2/)も使ってみたかったので、これも組み合わせた。

## 手順

jarファイル/クラスパスはmavenなりダウンロードなりでプロジェクトに設定済みであることを前提とする。なおHikariCPの動作には[Javassist](http://www.csg.ci.i.u-tokyo.ac.jp/~chiba/javassist/)（本稿では3.18.1-GAを使用）も必要。

### Propertiesファイルの作成

WicketはLoggerとしてslf4jを使っている。これをlog4jdbc-log4j2からも利用されるようにPropertiesファイルで設定する。[log4jdbc-log4j2のホームページ](https://code.google.com/p/log4jdbc-log4j2/)の"4.2. If you use SLF4J"を参考に、log4jdbc.log4j2.propertiesを作成すればよい。

```properties:log4jdbc.log4j2.properties
log4jdbc.spylogdelegator.name=net.sf.log4jdbc.log.slf4j.Slf4jSpyLogDelegator
```
次に、HikariCPもPropertiesファイルで設定する<sup><a name="fnref:1"></a>[1](#fn:1)</sup>。log4jdbc-log4j2をJDBCのラッパとして指定するには、[HikariCPのREADME.md](https://github.com/brettwooldridge/HikariCP)を参考に、`jdbcUrl`で設定を行った方が簡単と思われる。

```properties:hikari.properties
# 接続先は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"](http://wicket.apache.org/guide/guide/requestProcessing.html)の"Storing arbitrary objects with metadata"の項の例を参考に、WebApplicationのサブクラスでMetaDataのMapにSql2oのインスタンスが格納されるようにした（Sql2oインスタンスはthread-safeなので、複数threadが共有できる）<sup><a name="fnref:2"></a>[2](#fn:2)</sup>。

#### ハマりどころ
- hikari.propertiesは自前で読み込む必要がある
- QuirksModeをDBMSにあわせて指定する必要がある

```java:MyApplication
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のインスタンスを取得し、トランザクション処理を開始する。

```java

Sql2o sql2o = MyApplication.getDBPooling();
try (Connection con = sql2o.open()) {
  // 書籍テーブルからISBNのリストを取得する想定
  List<String> isbns = con.createQuery("select isbn from books")
    .executeScalarList(String.class);
}
````

SQLの実行結果が正常にisbnsに入っていることを確かめれば動作確認完了。

----
1. <a name="fn:1"></a>HikariCPではコード上での設定や他のファイル形式での設定もできるが、今回はlog4jdbc-log4j2でもPropertiesファイルを作るので統一した[&#8617;](#fnref:1)
2. <a name="fn:2"></a>DIなども使ったテストのしやすさを考えると、シングルトンな[&#8617;](#fnref:2)