前回の続きです。
前回同様、このページをそのままなぞっているだけです。
真面目にやりたい人は上記を見ることをおすすめします。
単純に、リクエストした内容をDBに登録するフローを試してみます。
routingについて
まずはルーティングについて確認しておきましょう。
conf/routes
GET /register/:id/:name controllers.Application.register(id: Long, name: String)
Actionの方にも定義を追加します。
def register(id: Long, name: String) = Action { Ok(views.html.register(id, name)) }
テンプレートは省略しますが、これで渡した内容を表示することができます。
Stringになっている部分はマルチバイト文字でも自動的にデコードして表示することができます。
型に合わないリクエスト(/register/abc/1 など)では、Bad Requestになります。
DB接続
mysql-connector-java の追加
build.sbtに設定を追加します。
build.sbt
libraryDependencies += "mysql" % "mysql-connector-java" % "5.1.27"
ここで一応activatorを再起動しました。
INSERT文実行
簡単なINSERT文を実行してみます。
テーブルは以下のように用意しています。
CREATE TABLE registers ( id bigint PRIMARY KEY NOT NULL, name varchar(512) NOT NULL, created_at datetime NOT NULL );
以下、差分のみです。
app/controllers/Application.scala
import play.api.Play.current import play.api.db._ def register(id: Long, name: String) = Action { val con = DB.getConnection() var result = "" try { val q_str = "INSERT INTO registers (id, name, created_at) VALUES (?, ?, CURRENT_TIMESTAMP) ; " val stmt = con.prepareStatement(q_str) stmt.setLong(1, id) stmt.setString(2, name) stmt.executeUpdate() result = "Success." } catch { case e:Exception => result = e.toString } finally { con.close() } Ok(views.html.register(id, name, result)) }
play.api.db._ はよいとして、play.api.Play.current をimportしないと、追加することを促すエラーを表示します。
You do not have an implicit Application in scope. If you want to bring the current running Application into context, just add import play.api.Play.current
「context中に暗黙のApplicationがない」ということですが、これはScalaの implicit という機能が肝のようです。
そしてこの play.api.Play.current が現在のアプリケーションの状態を保持しているもの、と理解して良さそうです(あまり正確ではなさそうですが)。
DBコネクションの設定情報などもこれを通して取得している、ということなのでしょうかね。
上記の import を追加して動かしてみます。
http://localhost:9000/register/1/東京都
にアクセスしてみました。
Success.
ID: 1
name: 東京都
idはPRIMARY KEYですので、リロードすると当然ですがDB側でエラーとなります。
com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Duplicate entry '1' for key 'PRIMARY'
ID: 1
name: 東京都
Migration/ORMについて
migrationについては、evolutionsというものがあるようですが、 日本語のドキュメントは前のバージョンなので、少し内容が違っているようです。
ORMについては、javaのebeanというのもあるようですが、 シンプルなSQLアクセスということで、Anormというものもあるようです。
最初にこうあります。
Anorm は ORM (Object Relational Mapper) ではない
随分と潔いですね。
ORMは確かに便利ですが、個人的には利用価値があまりないケースも多いと思っているので、 SQL直書きに近いこちらでやってみようと思います。
あらかじめ先ほどのテーブルは削除しています。
evolutions用のディレクトリを作成します。
$ mkdir -p conf/evolutions/default
依存関係を解決します。
このあたりは2.3系とはやり方が違っているようです。
build.sbt
libraryDependencies += "com.typesafe.play" %% "anorm" % "2.4.0"
libraryDependencies += evolutions
migrationのファイルはファイル名の番号で順番に実行していく形式のようです。
conf/evolutions/default/1.sql
### registers
# --- !Ups
CREATE TABLE registers (
id bigint PRIMARY KEY NOT NULL,
name varchar(512) NOT NULL,
created_at datetime NOT NULL
) ENGINE=innoDB ;
# --- !Downs
DROP TABLE registers ;
設定が済んだらactivatorを再起動してアクセスすると、以下のような画面になりました。
[Apply this script now!] ボタンを押せば、このCREATE文を実行するようになっています。
今度はレコードを登録する部分を変更してみます。
package controllers import play.api.Play.current import play.api._ import play.api.mvc._ import play.api.db.DB import play.Logger import anorm._ class Application extends Controller { def register(id: Long, name: String) = Action { var result_str = "" try { DB.withConnection { implicit c => val result = SQL( "INSERT INTO registers (id, name, created_at) VALUES({id}, {name}, CURRENT_TIMESTAMP) ; " ).on('id -> id, 'name -> name).executeInsert() result_str = "Success." } } catch { case e:Exception => result_str = e.toString() } Ok(views.html.register(id, name, result_str)) } }
importが増えたので、その部分も記述しています。
プレースホルダが比較的簡単に使えるのはよいですね。
…しかし、絶対的にScala言語に関する知識が不足しているので、エレガントとは程遠いコードになってしまいました。
次回はModelを作成してコントローラをすっきりさせたいと思います。