前回の続きです。

前回同様、このページをそのままなぞっているだけです。

真面目にやりたい人は上記を見ることをおすすめします。


単純に、リクエストした内容をDBに登録するフローを試してみます。

routingについて

まずはルーティングについて確認しておきましょう。

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を再起動してアクセスすると、以下のような画面になりました。

evolutions

[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を作成してコントローラをすっきりさせたいと思います。



blog comments powered by Disqus