reading anorm 2.0
DESCRIPTION
TRANSCRIPT
Reading Anorm 2.0
Kazuhiro Sera@seratch
#akskscala
Anorm のコンセプト
ドキュメントに明確に宣言されている
Anorm は ORM ではない
生 JDBC はつらい、よりよい API としての Anorm API
DB アクセスの DSL は SQL がベスト
SQL を生成する type safe DSL は誤った方向性だ
ORM と格闘するより SQL 書いた方がコスト小さい
Anorm API 利用例 1
import anorm._implicit val conn: java.sql.Connection = ...
// insertSQL(“insert into emp values ({id},{name},{age})”) .on(‘id -> 1, ‘name -> “Andy”, ‘age -> 19) .executeUpdate()
// updateSQL(“update emp set name = {name} where id = {id}”) .on(‘id -> 1, ‘name -> “Brian”) .executeUpdate()
Anorm API 利用例 2
// selectcase class Emp(id: Pk[Int], name: String, age: Option[Int])
import anorm.SqlParser._val emp: RowParser[Emp] = get[Pk[Int]](“id”) ~ get[String](“name”) ~ get[Option[Int]](“age”) map { case id ~ name ~ age => Emp(id, name, age) }
val emps: List[Emp] = SQL(“select * from emp”).as(emp.*)val emp: Option[Emp] = SQL(“select * from emp where id = {id}”) .on(‘id -> 1).as(emp.singleOpt)
Anorm は Play 非依存
JDBC を隠蔽した使いやすい API を提供することを目的にしていて Play 本体に依存していない
コネクションやトランザクションの管理はスコープ外
拙作の実例(scalikejdbc-with-anorm20.g8)
Play アプリだけでなく、ちょっとした DB アクセスが必要な場合にも便利に使える
Anorm のこれまで
はじまりは play-scala モジュールの機能として
2010/11 play.db.sql というパッケージで開始
2011/4 play.db.anorm に改名
2011/10 Play20 にそのまま追加
2012/1 大幅変更(Magic廃止など)
最近はあまり変更はされていない感じ
Play20 になって変わった点
play.db.anorm から anorm に package 変更
Magic が不要ということで廃止になった(やり取り)
Typesafe の Maven リポジトリで配布されている(“play” %% “anorm” % “2.0”)
Play 本体にある play.db API でトランザクション管理が提供されている(play-scala にはない)
ソースコードの構成
framework/src/anorm/src/main/scala
Anorm.scala
SqlParser.scala
SqlStatementParser.scala
TypeWrangler.scala
Utils.scala
ファイル構成はそのうち整理されるかも
Anorm.scala
SQL(String): SqlQuery、まず SqlQuery から読む
SqlQuery は暗黙の型変換で SimpleSql や BatchSql としてもふるまうので、これらも読む
クエリの場合、getFilledStatement でバインド変数を set して as(...) 内部で executeQuery() の流れ
package object がここにいるのはちょっと行儀よくない
Anorm API 利用例 3
case class Emp(id: Pk[Int], name: String, age: Option[Int])
val sql: SqlQuery = SQL(“select * from emp where id = {id}”)val query: SimpleSql[Row] = sql.on(‘id -> 1)val stream: Stream[Row] = query.apply()stream map { row => Emp(row[Pk[Int]]("id"), row[String]("name"), row[Option[Int]]("age")) }
SqlParser.scala
SqlParser の API が RowParser を返すので、それを「~」メソッドの呼び出しで結合して使う感じ
SqlParser.get[T](String): RowParser[T]
SqlParser.str(String): RowParser[String]
中置型 case class ~[+A, +B](_1: A, _2: B)
RowParser[A]#~[B](RowParser[B]): RowParser[A ~ B]
Anorm API 利用例 4
case class Emp(id: Pk[Int], name: String, age: Option[Int])
import anorm.SqlParser._
val rp: RowParser[Pk[Int] ~ String ~ Option[Int]] = get[Pk[Int]](“id”) ~ get[String](“name”) ~ get[Option[Int]](“age”) val emp: RowParser[Emp] = rp map { case id ~ name ~ age => Emp(id, name, age)}
val opt: ResultSetParser[Option[Emp]] = emp.singleOptval list: ResultSetParser[List[Emp]] = emp.*
SqlStatementParser.scala
JavaTokenParsers を extends したパーザーコンビネータな実装クラス
“select id,name,age from emp where id = {id} and name = {name}” のような SQL からバインド変数名({id}、{name})を順序付きで取り出す処理
2.0 時点では Sql.sql(String) でのみ使用
TypeWrangler.scala
Scala、Java の主要な型の scala.reflect.Manifest をつくって返すユーティリティ
Scala でいうと Byte、Short、Char、Int、Long、Float、Double、Boolean、Null、Unit が定義されている、対応する Java の型も同様
2.0 時点では使ってないようだが・・?
Utils.scala
現状だと MayErr しかない
case class MayErr[+E, +A](e: Either[E, A])
MayErr と Either は相互に暗黙の型変換
Anorm.scala を見ると MayErr がかなり使われている
play.db API
framework/src/play/src/main/scala/api/db/DB.scala
DBPlugin extends play.api.Plugin が Play の設定をもとに java.sql.DataSource を返す
object DB は内部的に DBApi(in DBPlugin)から java.sql.Connection をもらって withConnection、withTransaction ブロックを提供する
まとめ・感想
Magic がなくなって実装がかなりシンプルになった
反面、object Emp extends Magic[Emp] を定義するだけで使うことができた Emp.find(“id = {id}”).on(‘id -> 1) のような API がなくなった
Scala の DB アクセスライブラリの中では、かなり使い勝手はよい API になっていると思うので、Play アプリ以外でも使ってみてください