Slick(旧Scala Query)を使ったScalaによるDBアクセス

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

Scala QueryとSlick

ScalaQueryとは、ScalaでRDBを扱うための低レベルなライブラリです。
RDBのデータをコレクションのように扱うことができ、シンプルで自然な記述でDBへのアクセスができるので人気があります。

今回使用するSlickとは、このScalaQueryの後継として開発されているライブラリです。
ScalaQueryを汎用化し、RDB以外にもさまざまなデータソースを扱えるようにするのことを目標に開発されています。

環境構築

今回使用した動作環境は以下のとおりです。

  • OS : MacOS X 10.7.4
  • sbt : 0.12
  • Scala : 2.10.0 M7
  • Slick : 0.11.1

なお、プロジェクトのテンプレート作成のためgiter8を使用するので、Homebrewでインストールしておいてください。
※別にインストールしなくてもsbtのプロジェクトを手動でつくれば問題ないです

% brew install giter8

プロジェクトのセットアップ

ではまずsbtプロジェクトを作成しましょう。giter8を使用してsbtプロジェクトを作成します。
コンソールでいくつか質問されますが、とりあえず全部エンターキーを押します。

% g8 typesafehub/scala-sbt

scala-projectという名前でディレクトリが作成されたはずです。

次にsbtで依存ライブラリのインストールをしましょう。
scala-project/project/ScalaProjectBuild.scalaを下記のように編集します。
今回はDBにMySQLを使用するので依存ライブラリとしてslickとmysqlのドライバを追加します。
また、slickはScala 2.10でないと動作しないので、scalaVersionに2.10.0-M7を指定します。

import sbt._
import sbt.Keys._

object ScalaProjectBuild extends Build {

  lazy val scalaProject = Project(
    id = "scala-project",
    base = file("."),
    settings = Project.defaultSettings ++ Seq(
      name := "Scala Project",
      organization := "org.example",
      version := "0.1-SNAPSHOT",
      scalaVersion := "2.10.0-M7", //Scalaは2.10を使用
      // add other settings here
      libraryDependencies += "com.typesafe" % "slick_2.10.0-M7" % "0.11.1", //slick
      libraryDependencies += "mysql" % "mysql-connector-java" % "5.1.18" % "runtime" //mysql      
    )
  )
}

sbtコンソール上で依存ライブラリをダウンロードし、リロードしましょう。

% cd scala-project
% sbt
> update
> reload

最後にデータベースを作成しておきます。MySQLでデータベースを「mytest」という名前で作成しておきます。
これで準備ができました。

Slickを使ってみる

ではソースファイルを編集してslickを使ってみましょう。
すでにsrc/main/scala/org/example/ScalaProject.scalaファイルがあるので、このファイルを編集します。

必要なクラスのimport

まずは必要なクラスをimportしましょう。

import scala.slick.session.Database
import Database.threadLocalSession
import scala.slick.jdbc.{GetResult, StaticQuery => Q}
import Q.interpolation
import scala.slick.driver.MySQLDriver.simple._

Tableオブジェクトの作成

Appトレイトを継承して実行できるようにします。
次にTableを継承し、UserテーブルとCompanyテーブル用オブジェクトを作成します。
これらは実際のMySQLのテーブルと対応しています。(テーブルは後で作成)

object Main extends App {

  object User extends Table[(Int, Int,String, String, String)]("User") {
    def id = column[Int]("id", O.PrimaryKey) // This is the primary key column
    def companyId = column[Int]("companyId")
    def name = column[String]("name")
    def email = column[String]("email")
    def password = column[String]("password")
    // Every table needs a * projection with the same type as the table's type parameter
    def * = id ~ companyId ~ name ~ email ~ password
    def company = foreignKey("COMPANY_FK", companyId, Company)(_.id)
  }


  object Company extends Table[(Int,String)]("Company") {
    def id = column[Int]("id", O.PrimaryKey)
    def name = column[String]("name")
    def * = id ~ name
  }

プライマリキーや外部キーも定義可能です。
また、「*」を定義しておくことで、任意のカラムを指定した状態を「<Tableオブジェクト>.*」のように使うことができます。
(上記Companyの場合、idとnameのTupleとして扱える)

DBへの接続とテーブルの作成

次にDatabase.forURLで接続先を指定し、withSessionを呼びます。このブロック内でDBへの操作を行います。
このあたりはScalaQueryと同じです。

 
  Database.forURL("jdbc:mysql://localhost/mytest", driver = "com.mysql.jdbc.Driver",
    user="<ユーザー名>",password="<パスワード>") withSession {
    try {
      (Company.ddl ++ User.ddl).drop
    } catch {
      case _ => println("table not found")
    }
    (Company.ddl ++ User.ddl).create

また、Tableオブジェクトの持つddlトレイトに対してcreateやdropをすることで、DBのテーブルを作成したり削除することができます。
ここではCompanyテーブルとUserテーブルがなければ削除し、テーブルを作成しています。

テストデータの登録

データの登録はinsert関数かinsertAllでおこなうことができます。

    Company.insert(100,"cm")
    Company.insert(200,"annotation")

    User.insert(1, 100,"taro","taro@cm.com", "pass1")
    User.insert(2, 100,"mike","mike@cm.com", "pass2")
    User.insert(3, 100,"hanako","hanako@cm.com", "pass3")

    User.insertAll(
      (4,200,"takeshi","takeshi@cm.com","pass4"),
      (5,200,"jonny","jonny@cm.com","pass5")
    )

データの検索

ではUserを検索してみましょう。QueryにTableオブジェクトを渡してUserを全件取得しています。
また、for文とifガードを用いてSQLでテーブル同士を連結するように扱うことも可能です。

    println("Users:")
    Query(User) foreach { case (id,companyId,name,email,pass) =>
      println("  " + id + ":" + companyId + ":" + name + ":" + email + ":" + pass)
    }

    println("Manual join:")
    val q2 = for {
      u <- User if u.id > 1
      c <- Company if c.id === u.companyId
    } yield (u.name, c.name)
    for(t <- q2) println("  " + t._1 + " working in  " + t._2)
[/java]
</p>

<h3>データの更新と削除</h3>
<p>
Companyの名前が「cm」のものを検索し、そのレコードの名前を「new-cm」に変更しています。</br>
for文で返しているのがc.nameだけなので、指定するのもnameだけになっています。</br>
その後Userテーブルでidが2のデータを削除しています。whereでidを指定してdeleteメソッドを呼んでいるだけです。</br>

</p>


<h3>プログラムの実行</h3>
<p>
ではプログラムを実行してみましょう。</br>
sbtのコンソール上でrunコマンドを実行するとプログラムが実行されます。</br>

</p>
<p>
なお、SQLを直接実行したい場合は下記のようにStaticQueryを使用して直接実行することもできます。</br>

 StaticQuery.queryNA[User]("select * from User") foreach { u =>
   println("  " + u.id + "\t" + u.name + "\t")
 }

まとめ

いかがでしたか?コレクションを扱うように自然にRDBのアクセスができたかと思います。
最初は少しとっつきずらいところがあるかもしれませんが、関数型言語と同じく、慣れればとても使いやすいライブラリになるとおもいます。(たぶん)
また、Playframework 2.2では現在使用されているAnormとSlickを統合する予定もあるようで、ますます楽しみですね。