簡単アプリの開発(2)
(2)ソースコードのジェネレート
「登録」ボタンをクリックした時の処理を実装する前に、DBアクセス用オブジェクト「Tables」を作成します。「Tables」は、Slickのコードジェネレータを用いて作成することにします。Slickのコードジェネレータはバージョン「2.0」でリリースされた機能で、DBスキーマからソースコードをジェネレートすることができます。
SlickのコードジェネレータはScalaのAPIから実行する方法と、sbtから実行する方法の2通りがあります。今回は、先ほど作成したイベント登録画面に「Generate」ボタンを追加し、ScalaのAPI経由でジェネレートを実行します。本来は開発環境でのみボタンを表示するなどの考慮が必要ですが、説明を簡単にするため、今回はイベント登録画面からジェネレートが実行できるようにします。
「scala.slick.model.codegen.SourceCodeGenerator」の「main」メソッドを呼び出すことでジェネレータを実行することができます。「main」メソッドにはDB接続ユーザを指定する方法としない方法の2種類の呼び出し方法が準備されています。
(1)ユーザ/パスワードを指定しない場合
scala.slick.model.codegen.SourceCodeGenerator.main( Array(slickDriver, jdbcDriver, url, outputFolder, pkg) )
(2)ユーザ/パスワードを指定する場合
scala.slick.model.codegen.SourceCodeGenerator.main( Array(slickDriver, jdbcDriver, url, outputFolder, pkg, user, password) )
ジェネレータの実行に必要なパラメータは次のとおりです。「設定例」には、先ほど作成したDBスキーマからジェネレートする場合の設定内容を記述しています。
設定項目 | 説明 | 設定例 |
---|---|---|
slickDriver | Sclickドライバー名をフルパスで指定 | scala.slick.driver.H2Driver |
jdbcDriver | JDBCドライバー名をフルパスで指定 | org.h2.Driver |
url | DB接続URL | jdbc:h2:tcp://localhost:9092/demo;SCHEMA=TECHAPP |
outputFolder | 生成コード出力先 | app |
pkg | 生成コードのパッケージ | models |
user | DB接続ユーザ名 | sa |
password | DB接続パスワード | <空文字> |
今回は「TECHAPP」スキーマからソースコードをジェネレートしますが、現バージョンでは「url」内に記述したスキーマ名(SCHEMA=TECHAPP)は有効になりませんので、呼び出し部分をカスタマイズしてジェネレートすることにします(スキーマを指定しない(H2であれば「PUBLIC」スキーマを使用する)場合は、(2)の方法で呼び出すことができます)。
ジェネレータはDBスキーマから取得したテーブルの情報を元にソースコードを生成します。まず、テーブルの情報を取得する処理を「Model」に実装します。「models」パッケージを作成し、「Events.scala」を次のように実装します。
[Model]Events.scala(/app/models)
package models import play.api.db.DB import play.api.Play.current import scala.slick.driver.H2Driver.simple._ import scala.slick.jdbc.meta.{ MTable, createModel } import scala.slick.driver.JdbcDriver object Events { /** DBコネクション */ val database = Database.forDataSource(DB.getDataSource()) /** Modelの取得 */ def model = database.withSession { implicit session => val tables = MTable.getTables(None, Some("TECHAPP"), None, None).list createModel(tables, JdbcDriver) } }
「Database.forDataSource(DB.getDataSource())」とすることで、「application.conf」に設定したDBスキーマへのコネクションを取得することができます。「createModel」メソッドでジェネレートに必要な「scala.slick.model.Model」を取得し、戻り値として返します。「scala.slick.jdbc.meta.MTable」の「getTables」でテーブルのリストを取得しており、その第二引数でスキーマを指定することができます。オプション型で指定する必要がありますので、「Some("TECHAPP")」とします。
コントローラに「generate」メソッドを追加し、ジェネレータの実行処理を実装します。
[Controller]EventCreate.scala(/app/controllers/event)
package controllers.event import play.api._ import play.api.mvc._ import play.api.data.Form import play.api.data.Forms._ import scala.slick.model.codegen.SourceCodeGenerator import models.{ EventForm, Events } object EventCreate extends Controller { /** イベントフォーム */ val eventForm = Form( mapping( "eventId" -> text, "eventNm" -> text)(EventForm.apply)(EventForm.unapply)) /** 初期表示 */ def index = Action { Ok(views.html.event.eventCreate(eventForm)) } /** コード生成 */ def generate = Action { val slickDriver = "scala.slick.driver.H2Driver" val outputFolder = "app" val pkg = "models" val model = Events.model new SourceCodeGenerator(model).writeToFile(slickDriver, outputFolder, pkg) Ok(views.html.event.eventCreate(eventForm)) } }
「Events.scala」に作成した「Events.model」を呼び出し、「scala.slick.model.Model」を取得します。そして、「scala.slick.model.codegen.SourceCodeGenerator」の「writeToFile」でソースコードを出力します。slickDriverは「scala.slick.driver.H2Driver」を使用し、作成しているアプリケーション内にソースコードをジェネレートさせるように出力先フォルダは「app」とします。パッケージは「models」として生成するようにします。
ジェネレータ実行後は再度イベント登録の初期表示画面を表示するようにしておきましょう。
そして、イベント登録画面からジェネレートが実行できるよう、「generate」メソッドを呼び出すためのリンクをイベント登録画面に追加しておきましょう。
[View]eventCreate.scala.html(/app/views/event)
<a href="@controllers.event.routes.EventCreate.generate()" class="btn"> generate </a>
routesファイルにジェネレート用のURLを定義します。
routes(/conf)
GET /event/generate/ controllers.event.EventCreate.generate
それでは、イベント登録画面を表示し、「generate」ボタンをクリックしてソースコードのジェネレートを実行してみましょう。
ジェネレートを実行すると「models」パッケージに「Tables.scala」というファイルが生成されているはずなので、開いてみましょう。Eclipseからはプロジェクトを「リフレッシュ」するとファイルが見えるようになります。
Tables(/models)
package models // AUTO-GENERATED Slick data model /** Stand-alone Slick data model for immediate use */ object Tables extends { val profile = scala.slick.driver.H2Driver } with Tables /** Slick data model trait for extension, choice of backend or usage in the cake pattern. (Make sure to initialize this late.) */ trait Tables { val profile: scala.slick.driver.JdbcProfile import profile.simple._ import scala.slick.model.ForeignKeyAction // NOTE: GetResult mappers for plain SQL are only generated for tables where Slick knows how to map the types of all columns. import scala.slick.jdbc.{GetResult => GR} /** DDL for all tables. Call .create to execute. */ lazy val ddl = Event.ddl /** Entity class storing rows of table Event * @param id Database column ID AutoInc, PrimaryKey * @param eventId Database column EVENT_ID * @param eventNm Database column EVENT_NM */ case class EventRow(id: Int, eventId: String, eventNm: String) /** GetResult implicit for fetching EventRow objects using plain SQL queries */ implicit def GetResultEventRow(implicit e0: GR[Int], e1: GR[String]): GR[EventRow] = GR{ prs => import prs._ EventRow.tupled((<<[Int], <<[String], <<[String])) } /** Table description of table EVENT. Objects of this class serve as prototypes for rows in queries. */ class Event(_tableTag: Tag) extends Table[EventRow](_tableTag, Some("TECHAPP"), "EVENT") { def * = (id, eventId, eventNm) <> (EventRow.tupled, EventRow.unapply) /** Maps whole row to an option. Useful for outer joins. */ def ? = (id.?, eventId.?, eventNm.?).shaped.<>({r=>import r._; _1.map(_=> EventRow.tupled((_1.get, _2.get, _3.get)))}, (_:Any) => throw new Exception("Inserting into ? projection not supported.")) /** Database column ID AutoInc, PrimaryKey */ val id: Column[Int] = column[Int]("ID", O.AutoInc, O.PrimaryKey) /** Database column EVENT_ID */ val eventId: Column[String] = column[String]("EVENT_ID") /** Database column EVENT_NM */ val eventNm: Column[String] = column[String]("EVENT_NM") } /** Collection-like TableQuery object for table Event */ lazy val Event = new TableQuery(tag => new Event(tag)) }
Tablesトレイトに「Event」という名前のクラスと「EventRow」という名前のケースクラスが作成されています。各クラスにはテーブルのカラムに対応するプロパティが生成されており、それぞれのプロパティの型はテーブルのカラムの型を元に生成されています。「Integer型」の「ID」は「Int型」に、「VARCHAR型」の「イベントID」は「String型」にマッピングされます。「DATE型」の場合は「SQLDATE型」にマッピングされます。また、テーブルのカラムにNULL値が許容される(NOT NULL制約が付与されていない)場合は「Option型」になります。
今回は、特定のスキーマを利用してのジェネレートが必要だったため、ScalaのAPI経由で実行する方法を利用しました。sbtから実行すれば、コンパイルのタイミングでジェネレートタスクを実行するようにもできます。実行方法は、GitHub上の「slick-codegen-example」や「Typesafe Activator」の「"Using Slicks default code generator」テンプレートが参考になります。