前書き

PlayでDBを扱うための設定周りを書く。application.confとかevolutionsあたり。

Play2.0.4。DBはMySQL5.1を使用。

@CretedDate 2012/10/22
@Versions Scala2.9.2 Play2.0.4 MySQL5.1

Play2.0でのDB操作

Play2.0ではAnormという割と生な感じのライブラリを使って記述する。

AnormはRailsのActiveRecordとかとは違ってORMではない。Anormを使うとConnectionとかResultSetとかの生々しい子たちとはお別れできるけど、具体的なDB操作はSQLを直接書いて行う。

具体的にはこんな感じ。

DB.withConnection { implicit connection =>
  SQL("SELECT clm1, clm2 hoge WHERE id = {id}").on("id" -> 1).apply() foreach {
    case Row(clm1: String, clm2: String) => println(clm1, clm2)
  }
}

SQLを直で書く理由は、「RDB操作するDSLってわざわざ新開発しなくても、SQLが一番良くね? ベンダーごとに差があることを加味してもさ」というようなポリシーから来ているらしい。

実際問題としてORMでは間に合わないことは多いので、こういうはっきりした方向性は嫌いじゃないわ。

Squeryl等のORMライブラリを併用することは可能。そのうちSlickとかを絡める話も出てくるかもしれない。SlickはScala2.9系には対応してないので当面は無理そうだけど。

参考資料

Playをダウンロードしたフォルダの中のsamples/scala/computer-databaseにその手のサンプルが書かれている。

そこの動きを見ながらapp/models/Models.scalaとかconf/evolutionsの中とかを見るとなんとなく仕組みが分かった気持ちになる。

Evolutions(SQLを書いておく場所)に関する話はこの辺が参考になる。

プロジェクトを作る

とりあえず今回のサンプル用のプロジェクトとDBを作る。プロジェクト名とDB名は共にplay_example(仮)としておく。

プロジェクト作成。

$ play new play_example
$ cd play_example

データベース作成。

mysql> create database play_example;

準備完了。

MySQLに繋ぐ設定をする

MySQLでDBに接続することをPlayに伝える。

その辺りの設定はconf/application.confに書く。application.confには下記のような記述がある。

# db.default.driver=org.h2.Driver
# db.default.url="jdbc:h2:mem:play"
# db.default.user=sa
# db.default.password=

これをMySQLに繋ぐっぽい記述に編集する。データベース名は先ほど作成したplay_exampleを利用。

db.default.driver=com.mysql.jdbc.Driver
db.default.url="jdbc:mysql://localhost/play_example?useUnicode=yes&characterEncoding=UTF-8"
db.default.user=user
db.default.password=password

あと、mysql-connector-javaを依存jarに入れておく。project/Build.scalaを編集して、appDependenciesに太字の部分を追加。

    val appDependencies = Seq(
      // Add your project dependencies here,
      "mysql" % "mysql-connector-java" % "5.1.21"
    )

これで設定完了。

CREATE文を用意する

Playではconf/evolutions/${データベース名}の下に1.sql、2.sql、3.sqlのように連番でファイルを置くと、そのファイルが番号順に実行されるらしい。

データベース名はMySQLでのDB名ではなく、db.default.driverみたいに書いたとこのdefaultを指すようだ。

というわけで、conf/evolutions/default/1.sqlにCREATE文を書いてみる。

$ mkdir -p conf/evolutions/default
$ vi conf/evolutions/default/1.sql

下記のような感じでSQLを書く。

# --- !Ups

CREATE TABLE user (
  id int(10) NOT NULL AUTO_INCREMENT,
  name varchar(64) NOT NULL,
  email varchar(255) NOT NULL,
  PRIMARY KEY (id)
)  ENGINE=InnoDB DEFAULT CHARSET=utf8;

INSERT INTO user (name, email) VALUES ("おれおれ", "oreore@example.com");

# --- !Downs
DROP TABLE User

# --- !UpsにCREATEを、# --- !DownsにDROPを書いている。

DownsのところにDROP文を書いておくと、1.sqlを更新してサーバに繋いだ時に、DROPを実行してからCREATEを実行してくれる。

INSERT文も実行できるので、試しに書いておいた。

これならPlayを知らない人が見てもスキーマをすぐ理解できる。置かれている場所がconf/evolutionsという名前なのは、少しイメージしづらいかもしれないけど。

用意したCREATE文を実行する

evolutionにSQLを用意したら、play runでサーバを立ち上げる。

$ play run

立ち上がったらhttp://localhost:9000/にリクエストする。するとこんなページが出てくる。

localhost:9000

上のページを訳すと「defaultデータベースはevolutionを必要としている。下記のSQLを実行するぞ」みたいな。

右上のApply this script now!をクリックすると、画面に表示されたSQL(1.sqlのUpsの部分)が実行される。

1.sqlの内容を編集してもう一度ページを開くと、今度は少し内容が変わって「DROPしてからもう1回作り直すけど良い?」みたいな表示がされる。

Modelを用意する

Modelと言っても共通のModelクラスを継承してみたいなことはしない。普通にSQLを呼び出すだけのクラスを作ればいいらしい。

試しにapp/models/Users.scalaというファイルを作って、userに対してSELECT COUNT(*)する処理を書いてみる。

package models

import play.api.db._
import play.api.Play.current

import anorm._
import anorm.SqlParser._

object Users {
  def countAll: Long = {
    DB.withConnection { implicit connection =>
      SQL("SELECT COUNT(*) FROM user").as(scalar[Long].single)
    }
  }
}

あとはapp/controllers/Application.scalaから作ったメソッドを呼んでみる。view書くの面倒だからprintlnで済ます。

package controllers

import play.api._
import play.api.mvc._

object Application extends Controller {
  def index = Action {
    println( models.Users.countAll )
    Ok(views.html.index("Your new application is ready."))
  }
}

これでサーバを立ち上げたコンソール上に「1」と表示される。

Anormの詳しい使い方とかはまた別の機会に。