はじめに
Javaで実装したことある方には何のこともない話しなんですが・・・。
今までRDBに対する処理はslickに頼ってたんですよ。
でもトランザクション周りの処理をFrameworkに依存しないように切り離せないかなぁなんて考えはじめたんですが、slick内のDBIOAction
から上手いこと切り離せなかったんですよ。
そんなこんなでもうJavaのJDBCのDriverとHikariCPを直接使っちゃおうかとなったわけです。
ScalikeJDBC使えばいい? ・・・知ってるけど、今回は置いてきます。
環境
- scala: 2.12.8
- sbt: 1.2.8
- mysql-connector-java: 6.0.6
- HikariCP: 3.3.1
※JDBCとHikariCP自体の細かな解説はしません
環境構築
取りあえず手元にあるのがMySQLなのでそれで進めていきます。
Table
簡単なuser
テーブルを作成します。
CREATE TABLE `user` (
`id` BIGINT NOT NULL AUTO_INCREMENT,
`name` VARCHAR(50) DEFAULT NULL.
PRIMARY KEY (`id`)
);
SBT
sbtにデータベース周りの依存を追加。
libraryDependencies ++= Seq(
"mysql" % "mysql-connector-java" % "6.0.6",
"com.zaxxer" % "HikariCP" % "3.3.1"
)
HikariCP
まずはHikariCPを使えるようにします。この辺を参考にHikariConfig
を生成します。
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ライブラリ等を使えばいいでしょう。
んでHikariDataSource
を生成します。
val hikariDataSource = new HikariDataSource(hikariConfig)
~~~JDBC処理~~~
hikariDataSource.close()
元々がJavaなのでHikariDataSoruce
は明示的に閉じてやる必要があります。忘れるとコネクションプールがゾンビにでもなるのかも?
JDBC
上記、HikariDataSource
からjava.sql.Connection
を取得してJDBCを動かします。
・・・とその前に、クエリの結果をjava.sql.ResultSet
で受けるのですが、行に対する走査がwhile
を使ったループでちょっとscala的に残念な感じになるので拡張してmap
を関数を作っておきます。
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
を実装してみましょう。
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
パラメータで持つとか)が何となく掴めたのでよしとする。