Slick でテーブル定義のコードを自動生成する
Slick 2系では experimental 扱いではありますが、コード生成の機能が含まれていて、 めんどうなテーブル定義のコードを自動生成することができます。ちょっとやってみましょう。
テスト用のテーブル、こんな感じです。
slickcodegenexample=# CREATE TABLE users ( id serial PRIMARY KEY, first_name varchar(100) NOT NULL, last_name varchar(100) NOT NULL, birth_date timestamp NOT NULL );
コード生成機能は slick 本体の jar ではなく、slick-codegen と言う jar に含まれています。使用しているデータベースに合わせた jdbc driver も必要です。それぞれ build.sbt の libraryDependencies に加えて下さい。
name := """slick-codegen-example""" version := "1.0" scalaVersion := "2.11.1" libraryDependencies ++= Seq( "com.typesafe.slick" %% "slick" % "2.1.0", "com.typesafe.slick" %% "slick-codegen" % "2.1.0", "org.postgresql" % "postgresql" % "9.3-1102-jdbc41" )
コード生成のためのコードは以下のような単純なもので、SourceCodeGenerator.main
というメソッドを呼ぶだけです。
気をつけることは jdbc driver と slick のほうの driver を合わせることくらいでしょうか。今回は PostgreSQL を使っているので scala.slick.driver.PostgresDriver
を選びます。
package com.example import scala.slick.codegen.SourceCodeGenerator object SlickCodegen { def main(args: Array[String]): Unit = { val slickDriver = "scala.slick.driver.PostgresDriver" val jdbcDriver = "org.postgresql.Driver" val url = "jdbc:postgresql://localhost/slickcodegenexample" val outputFolder = "src/main/scala" val pkg = "com.example.models" SourceCodeGenerator.main( Array( slickDriver, jdbcDriver, url, outputFolder, pkg ) ) } }
実行すると、outPutFolder
で指定した場所にコードが生成されます。
見てみるといつも書いてるようなコードですね。
package com.example.models object Tables extends { val profile = scala.slick.driver.PostgresDriver } with Tables trait Tables { val profile: scala.slick.driver.JdbcProfile import profile.simple._ import scala.slick.model.ForeignKeyAction import scala.slick.jdbc.{GetResult => GR} lazy val ddl = Users.ddl case class UsersRow(id: Int, firstName: String, lastName: String, birthDate: java.sql.Timestamp) implicit def GetResultUsersRow(implicit e0: GR[Int], e1: GR[String], e2: GR[java.sql.Timestamp]): GR[UsersRow] = GR{ prs => import prs._ UsersRow.tupled((<<[Int], <<[String], <<[String], <<[java.sql.Timestamp])) } class Users(_tableTag: Tag) extends Table[UsersRow](_tableTag, "users") { def * = (id, firstName, lastName, birthDate) <> (UsersRow.tupled, UsersRow.unapply) def ? = (id.?, firstName.?, lastName.?, birthDate.?).shaped.<>({r=>import r._; _1.map(_=> UsersRow.tupled((_1.get, _2.get, _3.get, _4.get)))}, (_:Any) => throw new Exception("Inserting into ? projection not supported.")) val id: Column[Int] = column[Int]("id", O.AutoInc, O.PrimaryKey) val firstName: Column[String] = column[String]("first_name", O.Length(100,varying=true)) val lastName: Column[String] = column[String]("last_name", O.Length(100,varying=true)) val birthDate: Column[java.sql.Timestamp] = column[java.sql.Timestamp]("birth_date") } lazy val Users = new TableQuery(tag => new Users(tag)) }
データベースの timestamp 型が java.sql.Timestamp
型にマッピングされています。java.sql.Timestamp
型では少々使いにくい。Java 8 日時 API or joda-time が良さそうと思いますが、そういえば私は slick-joda-mapper というのを作っていました。これで timestamp 型を org.joda.time.DateTime
にマッピングさせましょう。
https://github.com/tototoshi/slick-joda-mapper
slick-codegen でのコード生成はこういう時のためにカスタマイズが可能になっています。まず先ほどの build.sbt に joda-time と slick-joda-mapper の依存性も加えます。
name := """slick-codegen-example""" version := "1.0" scalaVersion := "2.11.1" libraryDependencies ++= Seq( "com.typesafe.slick" %% "slick" % "2.1.0", "com.typesafe.slick" %% "slick-codegen" % "2.1.0", "org.postgresql" % "postgresql" % "9.3-1102-jdbc41", "joda-time" % "joda-time" % "2.4", "com.github.tototoshi" %% "slick-joda-mapper" % "1.2.0" )
コード生成のためのコードは次のようになります。
SourceCodeGenerater
を継承して、"java.sql.Timestamp"
を "DateTime"
に変更しています。
import scala.slick.{model => m} import scala.slick.codegen.SourceCodeGenerator import scala.slick.driver.JdbcProfile class CustomSourceCodeGenerator(model: m.Model) extends SourceCodeGenerator(model) { override def code = "import com.github.tototoshi.slick.PostgresJodaSupport._\n" + "import org.joda.time.DateTime\n" + super.code override def Table = new Table(_) { override def Column = new Column(_) { override def rawType = model.tpe match { case "java.sql.Timestamp" => "DateTime" case _ => { super.rawType } } } } } object CodeGen extends App { val slickDriver = "scala.slick.driver.PostgresDriver" val jdbcDriver = "org.postgresql.Driver" val url = "jdbc:postgresql://127.0.0.1/slickcodegenexample" val outputFolder = "src/main/scala" val pkg = "com.example.models" val driver: JdbcProfile = scala.slick.driver.PostgresDriver val db = { driver.simple.Database.forURL(url, driver = jdbcDriver) } db.withSession { implicit session => new CustomSourceCodeGenerator(driver.createModel()).writeToFile(slickDriver, outputFolder, pkg) } }
生成されたコードを見ると、さっき java.sql.Timestamp
だったところが DateTime
に変わっていることがわかります。
package com.example.models object Tables extends { val profile = scala.slick.driver.PostgresDriver } with Tables trait Tables { val profile: scala.slick.driver.JdbcProfile import profile.simple._ import com.github.tototoshi.slick.PostgresJodaSupport._ import org.joda.time.DateTime import scala.slick.model.ForeignKeyAction import scala.slick.jdbc.{GetResult => GR} lazy val ddl = Users.ddl case class UsersRow(id: Int, firstName: String, lastName: String, birthDate: DateTime) implicit def GetResultUsersRow(implicit e0: GR[Int], e1: GR[String], e2: GR[DateTime]): GR[UsersRow] = GR{ prs => import prs._ UsersRow.tupled((<<[Int], <<[String], <<[String], <<[DateTime])) } class Users(_tableTag: Tag) extends Table[UsersRow](_tableTag, "users") { def * = (id, firstName, lastName, birthDate) <> (UsersRow.tupled, UsersRow.unapply) def ? = (id.?, firstName.?, lastName.?, birthDate.?).shaped.<>({r=>import r._; _1.map(_=> UsersRow.tupled((_1.get, _2.get, _3.get, _4.get)))}, (_:Any) => throw new Exception("Inserting into ? projection not supported.")) val id: Column[Int] = column[Int]("id", O.AutoInc, O.PrimaryKey) val firstName: Column[String] = column[String]("first_name", O.Length(100,varying=true)) val lastName: Column[String] = column[String]("last_name", O.Length(100,varying=true)) val birthDate: Column[DateTime] = column[DateTime]("birth_date") } lazy val Users = new TableQuery(tag => new Users(tag)) }
slick-codegen は sbt に組み込んでコンパイル時にコード生成を行う、ということもでき、そのサンプルが https://github.com/slick/slick-codegen-example にあります。
まだ experimental なのでそこまでやるべきかは微妙なところですが、少なくともコード生成自体は普通に使えそうだなあと思いました。