この記事は公開されてから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)
データの更新と削除
Companyの名前が「cm」のものを検索し、そのレコードの名前を「new-cm」に変更しています。 for文で返しているのがc.nameだけなので、指定するのもnameだけになっています。 その後Userテーブルでidが2のデータを削除しています。whereでidを指定してdeleteメソッドを呼んでいるだけです。
println("update:")
val qu = for(c <- Company if c.name === "cm") yield c.name
val updated1 = qu.update("new-cm")
val q3 = for {
u <- User if u.id > 1
c <- Company if c.id === u.companyId
} yield (u.name, c.name)
for(t <- q3) println(" " + t._1 + " working in " + t._2)
println("delete:")
User.where(_.id === 2).delete
val q4 = for {
u <- User if u.id > 1
c <- Company if c.id === u.companyId
} yield (u.name, c.name)
for(t <- q4) println(" " + t._1 + " working in " + t._2)
}
}
プログラムの実行
ではプログラムを実行してみましょう。 sbtのコンソール上でrunコマンドを実行するとプログラムが実行されます。
> run
[info] Running org.example.Main
・・・
table not found
Users:
1:100:taro:taro@cm.com:pass1
2:100:mike:mike@cm.com:pass2
3:100:hanako:hanako@cm.com:pass3
4:200:takeshi:takeshi@cm.com:pass4
5:200:jonny:jonny@cm.com:pass4
Manual join:
mike working in cm
hanako working in cm
takeshi working in annotation
jonny working in annotation
update:
mike working in new-cm
hanako working in new-cm
takeshi working in annotation
jonny working in annotation
delete:
hanako working in new-cm
takeshi working in annotation
jonny working in annotation
なお、SQLを直接実行したい場合は下記のようにStaticQueryを使用して直接実行することもできます。
StaticQuery.queryNA[User]("select * from User") foreach { u =>
println(" " + u.id + "\t" + u.name + "\t")
}
まとめ
いかがでしたか?コレクションを扱うように自然にRDBのアクセスができたかと思います。 最初は少しとっつきずらいところがあるかもしれませんが、関数型言語と同じく、慣れればとても使いやすいライブラリになるとおもいます。(たぶん) また、Playframework 2.2では現在使用されているAnormとSlickを統合する予定もあるようで、ますます楽しみですね。