ScalaのSlickでデータベースに接続して、素のSQLでCRUDしたい



## 必要なパッケージ

build.sbtに書く

```scala:build.sbt
// slick
libraryDependencies ++= Seq(
  "mysql" % "mysql-connector-java" % "5.1.29",
  "com.typesafe.slick" %% "slick" % "2.1.0",
  "c3p0" % "c3p0" % "0.9.1.2"
)
```

## データベースへの接続方法

`DBConfiguration`クラスを作ってそこにまとめるのがいいかも。
`ComboPooledDataSource`でコネクションプーリングしておくことで、コネクションが切れても再接続されるらしい。

```scala
class DBConfiguration {
  import com.mchange.v2.c3p0.ComboPooledDataSource
  import scala.slick.driver.MySQLDriver.simple._

  val dbType = "mysql"
  val dbHost = "127.0.0.1"
  val dbPort = 3306
  val dbName = "mydb"
  val dbUser = "root"
  val dbPassword = "mypass"
  val dbDriver = "com.mysql.jdbc.Driver"
  val jdbcUrl = "jdbc:%s://%s:%d/%s?characterEncoding=UTF-8".format(dbType, dbHost, dbPort, dbName)

  lazy val database = {
    val ds = new ComboPooledDataSource
    ds.setDriverClass(dbDriver)
    ds.setJdbcUrl(jdbcUrl)
    ds.setUser(dbUser)
    ds.setPassword(dbPassword)
    ds.setInitialPoolSize(3)
    ds.setMaxPoolSize(30)
    ds.setTestConnectionOnCheckin(false)
    ds.setTestConnectionOnCheckout(true)
    Database.forDataSource(ds)
  }
}
```

## ドメインオブジェクト


ドメインオブジェクトはcase classで定義しておくだけ。

```scala
case class User(username: String, email: String)
```

## ドメインオブジェクトの永続化方法

ドメインオブジェクトの永続化の実装はひとつのクラスにまとめておくといい。いわゆる[DataMapper](http://martinfowler.com/eaaCatalog/dataMapper.html)パターン。

Slickのリスト操作風に書けるDSLを使ってもいいが、個人的な好みで素のSQLが書きたいのでここでは[Plain SQL Query](http://slick.typesafe.com/doc/2.1.0/sql.html)を使った。

このサンプルコードには`MySQLUsersTable`にはCRUDのすべての操作がある。

```scala
import scala.slick.jdbc.{ GetResult, StaticQuery => Q }

class MySQLUsersTable(config: DBConfiguration) {
  val db = config.database // alias

  // 結果の行とcase classをマッピングするための宣言。findAllとfindByUsernameでこれを使う
  implicit val getUserResult = GetResult(r => User(r.<<, r.<<))

  def createTable() = db.withSession { implicit session =>
    // NA は non arguments の略
    Q.updateNA(
      """
        |CREATE TABLE IF NOT EXISTS `users` (
        |  `username` varchar(255) NOT NULL DEFAULT '',
        |  `email` varchar(255) NOT NULL DEFAULT '',
        |  PRIMARY KEY (`username`)
        |) ENGINE=InnoDB DEFAULT CHARSET=utf8;
      """.stripMargin
    ).execute
  }

  def truncate() = db.withSession { implicit session =>
    Q.updateNA("TRUNCATE TABLE users").execute
  }

  def add(user: User) = db.withTransaction { implicit session =>
    // +? で文字列結合するとエスケープもしてくれる
    (Q.u + "INSERT INTO users (username, email) VALUES (" +? user.username + ", " +? user.email + ")").execute
  }

  def findAll: List[User] = db.withSession { implicit session =>
    (Q[User] + "SELECT * FROM users").list
  }

  def findByUsername(username: String): Option[User] = db.withSession { implicit session =>
    (Q[String, User] + "SELECT * FROM users WHERE username = ? LIMIT 1")(username).list.headOption
  }

  def update(user: User) = db.withTransaction { implicit session =>
    (Q.u + "UPDATE users SET email = " +? user.email + " WHERE username = " +? user.username).execute
  }

  def delete(username: String) = db.withTransaction { implicit session =>
    (Q.u + "DELETE FROM users WHERE username = " +? username).execute
  }
}
```

## クライアントコード

クライアントコードは次のようになる

```scala
val table = new MySQLUsersTable(new DBConfiguration)

table.createTable()
table.truncate()
table.add(User("alice", "alice@example.com"))
table.add(User("bob", "bob@example.com"))
table.add(User("carol", "carol@example.com"))

println(table.findAll)
println(table.findByUsername("alice")) // -> Some(User(...))

table.update(User("alice", "alice2@example.com"))

println(table.findByUsername("alice"))

table.delete("alice")

println(table.findByUsername("alice")) // -> None

```