Play2.5-scalikejdbc2.5s

DBの準備

ツールプロジェクトの準備 h2.zip をダウンロードし、以下のようにplay2-hands-onプロジェクトと同じディレクトリに展開します。 +-/play2-hands-on | | | +-/app | | | +-/conf | | | +-... | +-/h2 | +-create.sql | +-data.mv.db | +-... H2の起動 Windowsの場合 まず、h2/start.batをダブルクリックしてH2データベースを起動します。データベースには以下のスキーマのテーブルが作成済みの状態になっています。 Macの場合 cd h2/ sh start.sh ※起動後、そのターミナルは閉じないでください。 モデルの自動生成 ScalikeJDBCではタイプセーフなAPIを使用するためにモデルクラスを用意する必要がありますが、ScalikeJDBCがsbtプラグインとして提供しているジェネレータを使用することでDBスキーマから自動生成することができます。 play2-hands-onプロジェクトでScalikeJDBCの自動生成ツールを使えるようにします。project/plugins.sbtに以下の設定を追加します。 libraryDependencies += "com.h2database" % "h2" % "1.4.193" addSbtPlugin("org.scalikejdbc" %% "scalikejdbc-mapper-generator" % "2.5.1") また、project/scalikejdbc.propertiesというファイルを以下の内容で作成します。 # --- # jdbc settings jdbc.driver=org.h2.Driver jdbc.url=jdbc:h2:tcp://localhost/data jdbc.username=sa jdbc.password=sa jdbc.schema=PUBLIC # --- # source code generator settings generator.packageName=models # generator.lineBreak: LF/CRLF generator.

IDEの準備

プラグインのインストール Java7以降 + IntelliJは予めインストール済みとします。まずは以下の手順でIntelliJにScala開発用のプラグインを導入します。 メニュー[IntelliJ IDEA]→[Preferences]→[Plugins]→[Install JetBrains plugin…]をクリック [Scala]を選択し、右クリック[Download and Install]をクリック ※IntelliJ Ultimate EditionはPlayプラグインを使うことができ、PlayプロジェクトをIntelliJで作成したり、HTMLテンプレートや設定ファイルなどを編集するためのエディタが追加されます。 プロジェクトのインポート IntelliJのScalaプラグインはSBTプロジェクトをネイティブサポートしており、「File」メニューから「Open」を選択し、Play2プロジェクト内のbuild.sbtを選択するとSBTプロジェクトとしてインポートすることができます(プロジェクトのルートディレクトリを選択するとGradleプロジェクトとしてインポートされてしまいますので注意してください)。 インポートする際に以下のダイアログが表示されます。初回は「Project SDK」が未選択の状態になっているかもしれません。「New…」をクリックしてJDKがインストールされているディレクトリを選択してから「OK」をクリックしてください。 build.sbtを編集してライブラリを追加した場合、ウィンドウ右上に以下のようなメッセージが表示されます。 「Refresh」を選択するとプロジェクトが再インポートされ、ライブラリが自動的にインターネット経由でダウンロードされクラスパスに追加されます。また、「Enable auto-import」を選択するとbuild.sbtを変更するたびに自動的に再インポートされるようになります(プロジェクトのインポート時に自動インポートを有効にしておくことも可能です)。

JSON APIの準備

フロントエンドがAngularJSやスマートフォンアプリの場合、サーバサイドはJSONを返却するAPIを提供する必要があります。ここまでに作成してきたユーザ情報のCRUD処理について、Play2のJSONサポート機能を使ってJSONベースのWeb APIを実装します。 コントローラの雛形を作る controllersパッケージにJsonControllerオブジェクトを以下の内容で作成します。SlickやPlay2のJSONサポートを使用するためのimport文を予め含めています。 package controllers import play.api.mvc._ import play.api.libs.json._ import play.api.libs.functional.syntax._ import scalikejdbc._ import models._ class JsonController extends Controller { /** * 一覧表示 */ def list = TODO /** * ユーザ登録 */ def create = TODO /** * ユーザ更新 */ def update = TODO /** * ユーザ削除 */ def remove(id: Long) = TODO } UserControllerと比べると、テンプレートでの国際化機能のために必要だったMessagesApiのDIやI18nSupportトレイトのミックスインを行っていないため、非常にシンプルなクラス定義になっています。

Play 2.5 + ScalikeJDBC 2.5 ハンズオン

目的 Play2 + ScalikeJDBC を使ってWebアプリケーションを作成するハンズオンです。 主な目的は以下の通りです。 Scalaに触れてもらう 数時間でとりあえず動くものを作ってみる そのため、なるべくフレームワークが提供する機能をそのまま使います。 構成 使用するフレームワークおよびバージョンは以下の通りです。 Play 2.5.x ScalikeJDBC 2.5.x 前提条件 このハンズオンを実施するにあたっての前提条件は以下の通りです。 JavaおよびWebアプリケーションの開発に関する基本的な知識を持っていること JDK 1.7以降がインストールされていること EclipseもしくはIntelliJ IDEAの最新版がインストールされていること 内容 ユーザ情報のCRUDを行う簡単なアプリケーションを作成します。 ユーザ一覧を表示する 新規ユーザ登録を行う ユーザ情報を編集する ユーザを削除する また、後半ではこのアプリケーションと同じCRUD処理を行うJSONベースのWeb APIも作成します。

ScalikeJDBCでの実践的な検索処理

ここまでの例ではscalikejdbcGenで自動生成されたメソッドや単一テーブルに対するシンプルなQueryDSLで処理できる例ばかりでしたが、ここではもう少し実践的な検索処理の実装方法について紹介します。 INNER JOIN INNER JOINの場合はシンプルにinnerJoinメソッドを使用します。検索結果の取得もシンプルです。以下の例ではUSERSテーブルとCOMPANIESテーブルをINNER JOINし、検索結果をそれぞれのモデルクラスのタプルのシーケンスで取得しています。 val users: Seq[(Users, Companies)] = withSQL { select.from(Users as u).innerJoin(Companies as c).on(u.companyId, c.id) }.map { rs => (Users(u)(rs), Companies(c)(rs)) }.list.apply() LEFT JOIN LEFT JOINの場合はleftJoinメソッドを使用します。 val users: Seq[(Users, Option[Companies])] = withSQL { select.from(Users as u).leftJoin(Companies as c).on(u.companyId, c.id) }.map { rs => (Users(u)(rs), rs.intOpt(c.resultName.id).map(_ => Companies(c)(rs))) }.list.apply() LEFT JOINしたテーブルの値を取得する場合、map()メソッドでOption型に変換する必要があるという点に注意してください。以下のコードは、まず結果セットからCOMPANIESテーブルのIDカラムをintOptメソッドでOption[Int]型として取得し、値が取得できた場合のみCompaniesクラスにマッピングするという処理を行っています。 rs.intOpt(c.resultName.id).map(_ => Companies(c)(rs)) SQLを直接記述する sql interpolationを使うと文字列リテラルで生SQLを記述することができます。ただし、SQLを完全に記述するだけでなく、自動生成されたクラスを使って記述を補助することができます。 val users: Seq[(Users, Companies)] = sql""" |SELECT ${u.

プロジェクトの作成

sbtのインストール まずはsbtをインストールします。sbtはScalaの標準的なビルドツールです。 Windowsの場合 以下のリンクからインストーラをダウンロードしてインストールします。 https://dl.bintray.com/sbt/native-packages/sbt/0.13.13.1/sbt-0.13.13.1.msi Macの場合 Homebrewでインストールします。 brew update brew install sbt どちらの場合もインストール後以下のようにしてsbtコマンドが使えること、sbtのバージョンが0.13.13以降であることを確認してください。 sbt sbtVersion [info] Loading global plugins from /Users/naoki.takezoe/.sbt/0.13/plugins [info] Set current project to naoki-takezoe (in build file:/Users/naoki.takezoe/) [info] 0.13.13 新規プロジェクト作成 コマンドプロンプトで以下のコマンドを実行します。 sbt new playframework/play-scala-seed.g8 --branch 2.5.x プロジェクト名などを聞かれますが、ここではプロジェクト名をplay2-hands-onとし、他の項目は初期値のままプロジェクトを作成するものとします。 play2-hands-onディレクトリのbuild.sbtにORMとしてScalikeJDBCを使用するための設定を行います。 name := """play2-hands-on""" organization := "com.example" version := "1.0-SNAPSHOT" lazy val root = (project in file(".")).enablePlugins(PlayScala) scalaVersion := "2.11.8" libraryDependencies += filters libraryDependencies += "org.scalatestplus.play" %% "scalatestplus-play" % "1.

ユーザ一覧APIの実装

USERSテーブルからIDの昇順に全件取得し、ユーザ一覧をJSONで返します。 Writesの定義 Play2のJSONサポートでは、ScalaオブジェクトをJSONに変換するにはWrites、JSONをScalaオブジェクトに変換するためにはReadsを定義する必要があります。 ここではUSERSテーブルを検索して取得したケースクラスのリストをJSONに変換して返却するので、USERSテーブルに対応するUsersクラスに対応するWritesを定義しておく必要があります。画面から値を受け取るFormと同様、該当のコントローラ(ここではJsonController)のコンパニオンオブジェクトとして以下の内容を追加します。 object JsonController { // UsersをJSONに変換するためのWritesを定義 implicit val usersWrites = ( (__ \ "id" ).write[Long] and (__ \ "name" ).write[String] and (__ \ "companyId").writeNullable[Int] )(unlift(Users.unapply)) } Play2のJSONサポートが提供するDSLを使用してマッピングを定義していますが、DSLを使わずに以下のように記述することもできます。 implicit val usersWritesFormat = new Writes[Users]{ def writes(user: Users): JsValue = { Json.obj( "id" -> user.id, "name" -> user.name, "companyId" -> user.companyId ) } } POINT

ユーザ一覧の実装

USERSテーブルからIDの昇順に全件取得し、ユーザ一覧画面を表示します。 ビュー テンプレートはviewsパッケージに作成します。appディレクトリ配下にviews.userパッケージを作成し、以下の内容でlist.scala.htmlを作成します。 @* このテンプレートの引数 *@ @(users: Seq[models.Users])(implicit request: Request[Any]) @* テンプレートで利用可能なヘルパーをインポート *@ @import helper._ @* main.scala.htmlを呼び出す *@ @main("ユーザ一覧") { <div> <a href="@routes.UserController.edit()" class="btn btn-success" role="button">新規作成</a> </div> <div class="col-xs-6"> <table class="table table-hover"> <thead> <tr> <th>ID</th> <th>名前</th> <th>&nbsp;</th> </tr> </thead> <tbody> @* ユーザの一覧をループで出力 *@ @users.map { user => <tr> <td>@user.id</td> <td><a href="@routes.UserController.edit(Some(user.id))">@user.name</a></td> <td>@helper.

ユーザ削除APIの実装

指定したIDのユーザをUSERSテーブルから削除します。 コントローラ JsonControllerのremoveメソッドを以下のように実装します。 def remove(id: Long) = Action { implicit request => DB.localTx { implicit session => // ユーザを削除 Users.find(id).foreach { user => Users.destroy(user) } Ok(Json.obj("result" -> "success")) } } 実行 コマンドラインから以下のコマンドを実行してユーザが削除されることを確認してください。 curl -XPOST http://localhost:9000/json/remove/1

ユーザ登録・更新APIの実装

ユーザ情報をJSONで受け取り、登録もしくは更新を行います。 Readsの定義 JsonControllerのコンパニオンオブジェクトにユーザ情報を受け取るためのケースクラスと、JSONからそのケースクラスに変換するためのReadsを定義します。 object JsonController { ... // ユーザ情報を受け取るためのケースクラス case class UserForm(id: Option[Long], name: String, companyId: Option[Int]) // JSONをUserFormに変換するためのReadsを定義 implicit val userFormFormat = ( (__ \ "id" ).readNullable[Long] and (__ \ "name" ).read[String] and (__ \ "companyId").readNullable[Int] )(UserForm) } 前述のWritesと同様、DSLを使わずに以下のように記述することもできます。 implicit val userFormFormat = new Reads[UserForm]{ def reads(js: JsValue): UserForm = { UserForm( id = (js \ "id" ).