Playframework2.5 で Slick3 弄ってみる
まずはアプリケーション仕様
今回は特に何も考えず、Evolution で突っ込んだ文字列を画面に表示するだけ。
ジンプルなことは良いことだ。
教科書はこれ
Playframework プロジェクト作成
いつものこと
activator new
play-scala を選択する。
現時点で正式公開なのは、Play 2.5 なので、2.5 のテンプレができる。
厳密には 2.5.10 ができた(2017/3/9 22時現在)。
まずは写経してみる
liblaryDependencies に突っ込むところからとな。
当然 evolution も突っ込む。
build.sbt にて
val H2_VERSION = "1.4.193" libraryDependencies ++= Seq( jdbc, cache, ws, "com.typesafe.play" %% "play-slick" % "2.0.0", "com.typesafe.play" %% "play-slick-evolutions" % "2.0.0", "com.h2database" % "h2" % H2_VERSION, "org.scalatestplus.play" %% "scalatestplus-play" % "1.5.1" % Test )
次は DB 設定。
application.conf に下記を追加。
slick.dbs.default.driver="slick.driver.H2Driver$" slick.dbs.default.db.driver="org.h2.Driver" slick.dbs.default.db.url="jdbc:h2:mem:play"
db ディレクティブは参照しないらしい。
そっかー的な感覚。
そして、ここまでで起動と。
sbt run
するとエラー
1) A binding to play.api.db.DBApi was already configured at play.api.db.slick.evolutions.EvolutionsModule.bindings(EvolutionsModule.scala:15): Binding(interface play.api.db.DBApi to ConstructionTarget(class play.api.db.slick.evolutions.internal.DBApiAdapter) in interface javax.inject.Singleton) (via modules: com.google.inject.util.Modules$OverrideModule -> play.api.inject.guice.GuiceableModuleConversions$$anon$1). at play.api.db.DBModule.bindings(DBModule.scala:25): Binding(interface play.api.db.DBApi to ProviderConstructionTarget(class play.api.db.DBApiProvider)) (via modules: com.google.inject.util.Modules$OverrideModule -> play.api.inject.guice.GuiceableModuleConversions$$anon$1)
写経してこれとか不親切が過ぎんだろ。
これに対する答えが
It is very likely that you have enabled the jdbc plugin, and that doesn’t really make sense if you are using Slick for accessing your databases. To fix the issue simply remove the Play jdbc component from your project’s build.
なんか「Slack使ってんのになんでJDBCなんぞ有効にしてんだよ…」的なニュアンスを感じるが…。
まぁいい、build.sbt を修正
val H2_VERSION = "1.4.193" libraryDependencies ++= Seq( cache, ws, "com.typesafe.play" %% "play-slick" % "2.0.0", "com.typesafe.play" %% "play-slick-evolutions" % "2.0.0", "com.h2database" % "h2" % H2_VERSION, "org.scalatestplus.play" %% "scalatestplus-play" % "1.5.1" % Test )
Evolution を設定する
Play Slick supports Play database evolutions.
とのことなので、早速ページを見る。
conf/evolutions/default
ディレクトリを作成(番号).sql
を作成
# Example table schema # --- !Ups CREATE TABLE IF NOT EXISTS `messages` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `message` varchar(255) NOT NULL, `created_at` datetime NOT NULL, `updated_at` datetime NOT NULL, PRIMARY KEY (id) ); INSERT INTO `messages` (`message`, `created_at`, `updated_at`) VALUES ('Example message', '2017-03-09 13:03:41', '2017-03-09 13:03:41'); # --- !Downs DROP TABLE IF EXISTS messages;
いざ実行!
Database 'default' needs evolution!
よっしゃ〜!!……?
いつまでたってもEvolution が終わらない…(汗
ログみてなるほど、Evolution 実行後にアプリケーションが再起動してる…つまりそのタイミングでインメモリデータベースも…
設定書き換え。
slick.dbs.default.driver="slick.driver.H2Driver$" slick.dbs.default.db.driver="org.h2.Driver" slick.dbs.default.db.url="jdbc:h2:./db/play"
これで Evolution 通過。
DBアクセス
ということで、スキーマと Dao 作る
package models import java.sql.Timestamp import javax.inject.{Inject, Singleton} import play.api.db.slick.{DatabaseConfigProvider, HasDatabaseConfigProvider} import slick.driver.JdbcProfile import slick.lifted.ProvenShape import scala.concurrent.duration._ import scala.concurrent.{Await, Future} case class Message( id: Long, message: String, createdAt: Timestamp = new Timestamp(System.currentTimeMillis()), updatedAt: Timestamp = new Timestamp(System.currentTimeMillis()) ) @Singleton class Tables @Inject() (override protected val dbConfigProvider: DatabaseConfigProvider) extends HasDatabaseConfigProvider[JdbcProfile] { import driver.api._ class Messages(tag: Tag) extends Table[Message](tag, "messages") { def id = column[Long]("id", O.PrimaryKey, O.AutoInc) def message = column[String]("message") def createdAt = column[Timestamp]("created_at") def updatedAt = column[Timestamp]("updated_at") override def * : ProvenShape[Message] = (id, message, createdAt, updatedAt) <> (Message.tupled, Message.unapply) } val messages = TableQuery[Messages] def getAllMessages(): Seq[Message] = { import dbConfig.driver.api._ Await.result( db.run(messages.sortBy(_.id).result), 10 seconds ) } }
そしたらController から読んでやる。
package controllers import javax.inject._ import models.Tables import play.api.mvc._ @Singleton class HomeController @Inject() ( tables: Tables ) extends Controller { def index = Action { Ok(views.html.index(tables.getAllMessages().map(_.message).mkString(","))) } }
さぁって…と思って試して見ると
JdbcSQLException: テーブル "messages" が見つかりません
What's!?
仕方ないので、ファイルを H2 Database Engine よりダウンロードしてコンソールを起動。
そこで疑問氷解。
どうにもテーブル名やカラム名が大文字になってる。当然、見つからないと言われたSQL(ログから取得) select "id", "message", "created_at", "updated_at" from "messages" order by "id"
も通らない。
何これヒデエ(TT
試しにコンソール上で大文字かして見る。
> select ID, MESSAGE, CREATED_AT, UPDATED_AT from "MESSAGES" order by ID; ID MESSAGE CREATED_AT UPDATED_AT 1 Example message 2017-03-09 13:03:41.0 2017-03-09 13:03:41.0 (1 行, 9 ms)
FUUUUxxKK!!
OK わかったよ。
@Singleton class Tables @Inject() (override protected val dbConfigProvider: DatabaseConfigProvider) extends HasDatabaseConfigProvider[JdbcProfile] { import driver.api._ class Messages(tag: Tag) extends Table[Message](tag, "MESSAGES") { def id = column[Long]("ID", O.PrimaryKey, O.AutoInc) def message = column[String]("MESSAGE") def createdAt = column[Timestamp]("CREATED_AT") def updatedAt = column[Timestamp]("UPDATED_AT") override def * : ProvenShape[Message] = (id, message, createdAt, updatedAt) <> (Message.tupled, Message.unapply) } val messages = TableQuery[Messages] def getAllMessages(): Seq[Message] = { import dbConfig.driver.api._ Await.result( db.run(messages.sortBy(_.id).result), 10 seconds ) } }
これで実行成功...自宅で落ち着いてやると案外すんなり行くんだよねこういうの。