ScalaでJDBC+HikariCPを直接使ってみる

# はじめに
Javaで実装したことある方には何のこともない話しなんですが・・・。

今までRDBに対する処理は[slick](http://slick.lightbend.com/)に頼ってたんですよ。
でもトランザクション周りの処理をFrameworkに依存しないように切り離せないかなぁなんて考えはじめたんですが、slick内の`DBIOAction`から上手いこと切り離せなかったんですよ。

そんなこんなでもうJavaのJDBCのDriverとHikariCPを直接使っちゃおうかとなったわけです。

[ScalikeJDBC]
(http://scalikejdbc.org/)使えばいい? ・・・知ってるけど、今回は置いてきます。

## 環境
- scala: 2.12.8
- sbt: 1.2.8
- mysql-connector-java: 6.0.6
- HikariCP: 3.3.1

**※JDBCとHikariCP自体の細かな解説はしません**

# 環境構築
取りあえず手元にあるのがMySQLなのでそれで進めていきます。

## Table
簡単な`user`テーブルを作成します。

```sql
CREATE TABLE `user` (
    `id` BIGINT NOT NULL AUTO_INCREMENT,
    `name` VARCHAR(50) DEFAULT NULL.
    PRIMARY KEY (`id`)
);
```

## SBT
sbtにデータベース周りの依存を追加。

```scala:build.sbt
libraryDependencies ++= Seq(
  "mysql"                      % "mysql-connector-java"     % "6.0.6",
  "com.zaxxer"                 % "HikariCP"                 % "3.3.1"
)
```

# HikariCP
まずはHikariCPを使えるようにします。[この辺](https://github.com/brettwooldridge/HikariCP/wiki/MySQL-Configuration)を参考に`HikariConfig`を生成します。

```scala
val hikariConfig = new HikariConfig()

hikariConfig.setDriverClassName("com.mysql.cj.jdbc.Driver")
hikariConfig.setJdbcUrl("jdbc:mysql://127.0.0.1:3306/mms?useSSL=false&")
hikariConfig.addDataSourceProperty("user", "root")
hikariConfig.addDataSourceProperty("password", "1234")
hikariConfig.addDataSourceProperty("cachePrepStmts", "true")
hikariConfig.addDataSourceProperty("prepStmtCacheSize", "250")
hikariConfig.addDataSourceProperty("prepStmtCacheSqlLimit", "2048")
hikariConfig.addDataSourceProperty("useServerPrepStmts", "true")
hikariConfig.addDataSourceProperty("useLocalSessionState", "true")
hikariConfig.addDataSourceProperty("rewriteBatchedStatements", "true")
hikariConfig.addDataSourceProperty("cacheResultSetMetadata", "true")
hikariConfig.addDataSourceProperty("cacheServerConfiguration", "true")
hikariConfig.addDataSourceProperty("elideSetAutoCommits", "true")
hikariConfig.addDataSourceProperty("maintainTimeStats", "true")
```

バリッバリにハードコーディングしてますが、実際使うときは[configライブラリ](https://github.com/lightbend/config)等を使えばいいでしょう。

んで`HikariDataSource`を生成します。

```scala
val hikariDataSource = new HikariDataSource(hikariConfig)

~~~JDBC処理~~~

hikariDataSource.close()
```

元々がJavaなので`HikariDataSoruce`は**明示的に閉じてやる必要があります。**忘れるとコネクションプールがゾンビにでもなるのかも？

# JDBC
上記、`HikariDataSource`から`java.sql.Connection`を取得してJDBCを動かします。

・・・とその前に、クエリの結果を`java.sql.ResultSet`で受けるのですが、行に対する走査が`while`を使ったループでちょっとscala的に残念な感じになるので拡張して`map`を関数を作っておきます。

```scala
implicit class RichResultSet(self: ResultSet) {
  def map[T](f: ResultSet => T): Seq[T] = {
    val buf = ListBuffer.empty[T]
    
    while (self.next()) {
      buf += f(self)
    }

    buf
  }
}
```

それでは環境構築で作成した`user`テーブルに対してトランザクション付きで`SELECT`と`INSERT`を実装してみましょう。

```scala
val hikariDataSource = new HikariDataSource(hikariConfig)

val connection = hikariDataSource.getConnection()

try {
  connection.setAutoCommit(false)

  val selectStmt = connection.prepareStatement("SELECT * FROM user")
  val selectResultSet = selectStmt.executeQuery()
  selectResultSet.map(row => (row.getLong("id"), 
  row.getString("name"))).foreach(println)
  selectResultSet.close()

  val insertStmt = connection.prepareStatement("INSERT INTO user(name) VALUES(?)")
  insertStmt.setString(1, "TEST")
  val affectedRows = insertStmt.executeUpdate()
  println(affectedRows)

  connection.commit()
} catch {
  case _ =>
    connection.rollback()
}

connection.close()

hikariDataSource.close()
```

・・・雑。

`java.sql.ResultSet`や`java.sql.Connection`も`HikariDataSource`同様に明示的なクローズ処理が必要です。もし本当に使う場合は`LoanPattern`等を利用するのが無難でしょう。

# おわりに
やっぱり`slick`や`ScalikeJDBC`って便利なんだぁとしみじみ感じてみたり。

ただ、課題だったトランザクションの分離については、実際に書いてみたことで
十分に実現可能なこと（`LoanPattern`で追い出すとか、`implicit`パラメータで持つとか）が何となく掴めたのでよしとする。