Play2.8-scalikejdbc3.4s

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.200" addSbtPlugin("org.scalikejdbc" %% "scalikejdbc-mapper-generator" % "3.4.0") また、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の準備

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

JSON APIの準備

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

Play 2.8 + ScalikeJDBC 3.4 ハンズオン

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

Play2のテスト

Play2はPlay2で開発されたアプリケーションのユニットテストのための機能を提供しています。デフォルトでHomeControllerに対するテストケースが生成されていますが、ここではハンズオンで作成したJSON APIに対するテストを作成してみましょう。 test/controllersに以下の内容でJsonControllerSpecを作成します。 package controllers import org.scalatestplus.play._ import org.scalatestplus.play.guice._ import play.api.libs.json._ import play.api.test._ import play.api.test.Helpers._ class JsonControllerSpec extends PlaySpec with GuiceOneAppPerSuite with Injecting { "JsonController GET" should { "request users list from the application" in { // Guiceからコントローラーのインスタンスを取得 val controller = inject[JsonController] // FakeRequestを使ってコントローラーのメソッドを呼び出す val result = controller.list.apply(FakeRequest()) // レスポンスのステータスを確認 status(result) mustBe OK // レスポンスのContent-Typeを確認 contentType(result) mustBe Some("application/json") // レスポンスのJSONを確認 val resultJson = contentAsJson(result) val expectedJson = Json.

ScalikeJDBCのテスト

scalikejdbcGenではソースコード生成時に対応するテストコードも自動生成してくれます。テストコードはtest/modelsパッケージにあります。 このテストコードを少し編集して、実際にテストを動かしてみましょう。 今回はテスト用のDBh2/test.mv.dbを用意していますので、こちらを使うようにします。 まず、build.sbtにscalikejdbc-testライブラリを追加します。 libraryDependencies += "org.scalikejdbc" %% "scalikejdbc-test" % "3.4.0" % Test 次に、テスト用のDBを読み込む準備をします。方法はいくつかありますが、今回はconf/test.confというファイルを以下の内容で作成します。 include "application.conf" db.default.url="jdbc:h2:tcp://localhost/test" テストではこちらの設定を読み込むようbuild.sbtに以下を追加します。 javaOptions in Test += "-Dconfig.file=conf/test.conf" 初期化処理をテストコードに追加します。 class UsersSpec extends fixture.FlatSpec with Matchers with AutoRollback { // 初期化処理を追加 config.DBs.setup() val u = Users.syntax("u") ... } class CompaniesSpec extends fixture.FlatSpec with Matchers with AutoRollback { // 初期化処理を追加 config.DBs.setup() val c = Companies.syntax("c") ... } 最後に、テストケースをハンズオンの形式に合わせて修正します。

ジョインの必要な処理

ここまではscalikejdbcGenで自動生成されたメソッドやシンプルなQueryDSLを使用してきましたが、テーブルのジョインが必要なケースについても実装してみましょう。 ユーザの一覧画面に以下のように会社名を表示するようにしてみます。 コントローラの修正 まずはUserControllerのlistメソッドを以下のように修正します。 def list = Action { implicit request => DB.readOnly { implicit session => // ユーザのリストを取得 val users = withSQL { select.from(Users as u).leftJoin(Companies as c).on(u.companyId, c.id).orderBy(u.id.asc) }.map { rs => (Users(u)(rs), rs.intOpt(c.resultName.id).map(_ => Companies(c)(rs))) }.list.apply() // 一覧画面を表示 Ok(views.html.user.list(users)) } } 外部結合したテーブルの値を取得する場合、map()メソッドでOption型に変換する必要があるという点に注意してください。以下のコードは、まず結果セットからCOMPANIESテーブルのIDカラムをintOptメソッドでOption[Int]型として取得し、値が取得できた場合のみCompaniesクラスにマッピングするという処理を行っています。 rs.intOpt(c.resultName.id).map(_ => Companies(c)(rs)) なお、内部結合の場合はleftJoinの代わりにinnerJoinメソッドを使用します。この場合、map()メソッドでOption型に変換する必要はありません。 ビューの修正 続いてlist.scala.htmlを以下のように修正します。検索結果の型がList[Users]からList[(Users, Option[Companies])]に変わり、表に「会社名」という列を追加しています。 @(users: Seq[(models.Users, Option[models.Companies])])(implicit request: RequestHeader) @import helper._ @main("ユーザ一覧") { <div> <a href="@routes.

プロジェクトの作成

sbtのインストール まずはsbtをインストールします。sbtはScalaの標準的なビルドツールです。 Windowsの場合 以下のリンクから最新のmsiファイルをダウンロードしてインストールします。 https://github.com/sbt/sbt/releases Macの場合 Homebrewでインストールします。 brew update brew install sbt どちらの場合もインストール後以下のようにしてsbtコマンドが使えること、sbtのバージョンが1.3以降であることを確認してください。 sbt sbtVersion [info] 1.3.4 新規プロジェクト作成 コマンドプロンプトで以下のコマンドを実行します。 sbt new playframework/play-scala-seed.g8 --branch 2.8.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.13.1" libraryDependencies += guice libraryDependencies += "org.scalatestplus.play" %% "scalatestplus-play" % "5.0.0" % Test // ↓↓↓↓ここから追加↓↓↓↓ libraryDependencies ++= Seq( "com.h2database" % "h2" % "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: RequestHeader) @* テンプレートで利用可能なヘルパーをインポート *@ @import helper._ @* main.scala.htmlを呼び出す *@ @main("ユーザ一覧") { <div> <a href="@routes.UserController.edit()" class="btn btn-success" role="button">新規作成</a> </div> <div class="col-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.