LoginSignup
30
30

More than 5 years have passed since last update.

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

Posted at

必要なパッケージ

build.sbtに書く

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でコネクションプーリングしておくことで、コネクションが切れても再接続されるらしい。

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で定義しておくだけ。

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

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

ドメインオブジェクトの永続化の実装はひとつのクラスにまとめておくといい。いわゆるDataMapperパターン。

Slickのリスト操作風に書けるDSLを使ってもいいが、個人的な好みで素のSQLが書きたいのでここではPlain SQL Queryを使った。

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

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
  }
}

クライアントコード

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

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

30
30
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
30
30