AnormはJDBCインスタンスを利用して、シンプルなDBレコード操作を提供する。
JDBCで書くSQLとの違いは、
- ScalaオブジェクトにマッピングするParserを定義することで、ユーザが定義したcase class型のListを作成することが簡略化できる。
- PreparedStatementのplaceholderに
{bindKey}
のように名前付きのplaceholderが定義できる
Anormの導入
build.sbt
libraryDependencies += jdbc libraryDependencies += "org.postgresql" % "postgresql" % "42.2.12" libraryDependencies += "org.playframework.anorm" %% "anorm" % "2.6.5"
DBドライバはRDBに応じたドライバを選択すればよい。
また、JDBCのインスタンスを利用するのでapplication.conf
に
db.default.driver=org.postgresql.Driver db.default.url="jdbc:postgresql://localhost:5432/dbname" db.default.user=username db.default.password=userpassword
を定義しておく
AnormでSQLを記述する
JDBCインスタンスはControllerより@Injectで受け取ればよい。
class HomeController @Inject()(db:Database, val controllerComponents: ControllerComponents) extends BaseController {
Insert
以下のように記述することでPreparedStatementに変数をセットして実行できる。
db.withConnection{ implicit connection => val sequence = SQL("Insert into yourTableName (column1, column2)values({column1}, {column2})") .on("column1" -> "value1", "column2" -> "value2") .executeInsert() }
executeInsert()
はdatabaseConnectionを受け取るんだけど、implicitの引数定義なので、implicit connection =>
とconnectionをimplicitとして定義しておけば引数の記述を省略できる。
sequence
にはinsertしたprimary keyが返されていた。
Update & Delete
以下の例はDeleteの例だけど、Updateも中のSQLが変わるだけで、使用するメソッドに変わりはない。
db.withConnection{ implicit connection => val affectedRows = SQL("delete from yourTableName where id = {id}").on("id" -> 100).executeUpdate() }
多分、executeUpdate()
の結果は変更を与えた行数が返されるのだと思う。
Select
Selectはまず結果cursolのParserを用意しておく。Parser連結末尾の~(チルダ)を忘れずに。
例としてJoinしたSelect結果に対応したParserを作成してみた。
case class JoinedRecord(id:Int, field1:String, field2:String, field3:String) val columnParser = { SqlParser.int("yourTableName.id") ~ // .column1は取り出すcolumn名を記述する SqlParser.str("yourTableName.column1") ~ SqlParser.double("yourTableName.column2") ~ SqlParser.double("joinedTableName.column3") }map{ case id ~ column1 ~ column2 ~ column3 => JoinedRecord(id, column1, column2, column3) }
上記のパーサーは問い合わせ結果の行
をJoinedRecordというcase classのフィールドに変換するパーサーとなる。
各SqlParser
に与える引数はtable名.カラム名
となる。
Selectの実行は
db.withConnection{ implicit connection => val result:List[JoinedRecord] = SQL("select * from yourTableName join joinedTableName on yourTableName.id = joinedTableName.id").as(columnParser.*) }
末尾の*(アスタリスク)
は結果を全行取得するという意味。
実行結果は定義したcase class JoinedRecord
型のListが返却される。
トランザクション
これはAnormというかJDBCなんだけど、transactionを宣言する時は、db.withTransaction()
でbegin宣言をしたconnectionが高階関数に渡される。なので、このconnectionを使用してSQLを発行すればよい。SQL等が失敗して例外throwした時は自動的にrollbackされる。
db.withTransaction{ implicit connection => }
また以下のように明示的にtransactionをcommit or rollbackすることもできる。
// db.withTransaction{ implicit connection =>と同義 implicit val connection = db.getConnection(false) try { SQL().on().executeUpdate() connection.commit() }catch{ case e:SQLException => connection.rollback }
その他
ControlleにJDBCインスタンスを@Injectする性質上、Fat Controllerに陥りやすい。
注入されたDBインスタンスは別途、Repositoryクラスなどを用意して、そちら側で使用するのが良いと思う。
複数のRepository間でbeginされたconnectionを取り回す。など必要な場合はRepositoryはConnection
タイプを受け取る設計でも良いと思う。