Upload
chibochibo
View
2.648
Download
1
Embed Size (px)
Citation preview
1
Slick入門
Takako Shimamoto
BizReach, Inc.
2
アジェンダ
• Scalaの代表的なORM
• Slickの基本的なお話
– スキーマ定義
– クエリ
• SQLを直接書く方法
– Plain SQL
– その他の選択肢
• Play2 + Slickで使うには
– 設定
– コネクションまわり
• プロジェクトでの留意点
3
Scalaで使えるORM
• Slick(旧ScalaQuery)
– タイプセーフなDSLや、SQLも記述可能
– 極力Scalaのコレクションと同じように扱えるよう設計されている
• Squeryl
– SQLは使用せず、DSLでクエリを記述する
– コンセプトは、できるだけシンプルに
val query =
from(table)
(t => where(t.id === id) select(t))
4
Scalaで使えるORM
• Anorm
– SQLを直接書くスタイル
– 手動でORマッピングを行う
• Scala ActiveRecord
– Squerylをベースに、モデル定義やよく使う機能をRailsのActiveRecordに似せている(CoC、DRY)
SQL("select * from User")
.as( int("id") ~ str("name") map {
case id~name => Person(id, name)
} *)
5
他にも、Activate、ScalikeJDBC、SORM など・・・
本日はTypesafe社お墨付きの
Slickについてお話します
6
Slickのきほん
7
タイプセーフなクエリ
• Slickは以下のように、クエリをタイプセーフに記述できることが1つの特徴
• このクエリを書くためには、スキーマ定義が必要
val id = 1L
val user: Option[UsersRow] =
Users.filter(_.id is id.bind).firstOption
select * from USERS x1 where x1.ID = ?
SQLのイメージ
8
スキーマ定義
• でも、手動で書くのは面倒ですよね
// マッピングするケースクラスcase class UserRow(id: Long, name: String)
// テーブルのスキーマ定義class User(tag: Tag) extends Table[UserRow](tag, "USER") {
def * = (id, name) <> (UserRow.tupled, UserRow.unapply)
val id: Column[Long] = column[Long]("ID", O.PrimaryKey)
val name: Column[String] = column[String]("NAME")
}
// クエリに使用lazy val User = new TableQuery(tag => new User(tag))
9
実はジェネレータがあるんですよ
10
コードジェネレータ
• sbtのタスクとして実行
• 生成するファイル
– Tables.scala
• 生成されるもの
– テーブル定義
– マッピングするケースクラス
– TableQuery
• クエリで使用
– 全テーブルのDDL
– GetResult(ResultSetからケースクラスに変換)• Plain SQLで使用
11
コードジェネレータ
• ジェネレータはカスタマイズ可能
– SourceCodeGeneratorを拡張する形
– ドキュメントがないので拡張ポイントがわかりにくい
– slick.model.codegenパッケージ配下を見るとよい
• BizReachではカスタマイズして使っている
– 基本的なCRUD機能
– シールドクラスとカラムのマッピング
– 楽観的排他による更新機能
12
これでクエリが書けます
13
基本的なCRUD
// 全件取得val users: Seq[UserRow] = User.list
// 登録val res: Int = User insert UserRow(1, "なまえ")
// 更新val res: Int = User.filter(_.id is id.bind).update(
UserRow(1, "なまえ変更"))
// 削除val res: Int = User.filter(_.id is id.bind).delete
14
基本的なCRUD
// 全件取得val users: Seq[UserRow] = User.list
// 登録val res: Int = User insert UserRow(1, "なまえ")
// 更新val res: Int = User.filter(_.id is id.bind).update(
UserRow(1, "なまえ変更"))
// 削除val res: Int = User.filter(_.id is id.bind).delete
bindを呼ぶとバインド変数になる
15
バインド変数になるbind
• bindなし
• bindあり
val name = "ta'kako"
User.filter(_.name is name).firstOption
・・・ from USER x1 where x1.NAME = 'ta''kako'
SQLのイメージ
User.filter(_.name is name.bind).firstOption
・・・ from USER x1 where x1.NAME = ?
SQLのイメージ
16
ケースクラスにマッピング
• <>を使えば、mapで絞り込んだ項目を別のケースクラスにマッピングできる
// このケースクラスにマッピングcase class Test(id: Long, name: String)
val res: List[Test] = User.map { t =>
t.id -> t.name <> (Test.tupled, Test.unapply)
}.list
17
他にもいろいろ
• ソート、グルーピング、ページング など
• 内部結合、外部結合も可能
• slick-referenceをGitHubに公開中
https://github.com/bizreach/slick-reference
18
タイプセーフなクエリで書けないときは?
19
Plain SQL
• SQLを文字列リテラルで書く
• 局所的に使うには便利
• ただ、複雑なSQLの代替として使うには厳しい
– リファクタリングがやりにくい
– 動的なSQLを組み立てるためには文字列処理が必要
sql"""SELECT ID + 1
FROM ISSUE_ID
WHERE USER_NAME = $owner FOR UPDATE
""".as[Int].firstOption
20
その他の選択肢
• mirage-scala
– Seasar2の2WaySQLの機能を単独のライブラリとして利用できるようにしたもの
– Slickと組み合わせて使用することも可能
val books: List[Book] = sqlManager.getResultList[Book](
Sql("""
SELECT BOOK_ID, BOOK_NAME, AUTHOR, PRICE
FROM BOOK
/*IF author!=null*/
WHERE AUTHOR = /*author*/'test'
/*END*/
"""), Map("author"->"Takako Shimamoto"))
パラメータはMapではなくケースクラスで与えることも可能
条件分岐やパラメータをSQLのコメントで記述するのでコピペしてそのままDB
に流して動作確認できる外部ファイル化することも可能
21
Play2でSlickを使うには
22
play-slick
• Play2でSlickを使う上で、面倒なことをいろいろやってくれるプラグイン
– Play2の設定から自動的にSlickのDatabase.forDataSourceを呼び出してくれる
– Slickのセッション管理を自動的にやってくれる
• withSessionやwithTransactionを呼び出してくれる
– Play2のリクエストとSlickのセッションの両方を持ち合わせたDBSessionRequestが使える
SlickがPlay2に組み込まれるのと同時にこのプラグインも統合される予定
23
何が変わるの?
• コントローラでPlay2のActionの代わりに、play-slick
のDBActionを使う
• トランザクションを開始する場合はDBAction.transactionを使う
def list = DBAction { implicit rs =>
// IDの昇順にすべてのユーザ情報を取得val users = Users.sortBy(t => t.id).list
// 一覧画面を表示Ok(views.html.user.list(users))
}
24
コネクションプール
• Slickはコネクションプールの実装を持っていない
• Play2と組み合わせて使うならBoneCP
• ただし、有名なリーク問題が未だにある
– https://bugs.launchpad.net/bonecp/+bug/999114
• 対処方法は
– BoneCPのバージョンを固定
– maxConnectionAgeを0にする
• 現在は、HikariCPをお試し中
25
プロジェクトで使う上で注意することは?
26
留意事項
• アグレッシブな機能追加/削除
– Scala全体の傾向(Slickも例外ではない)
– 特にメジャーアップにはマイグレーションが必要
– 常にキャッチアップして対応していく必要がある
• マニュアルがあまり豊富でない
– 本家サイトには書いてないが、できることが色々ある
– 慣れるまでに多少の時間が必要
– 充実させようとしている動きはある
– BizReachで公開しているリファレンスをぜひ活用を
27
留意事項
• 結合は発行されるSQLに注意
– 単一テーブルなら、①fiterで絞り込む、②mapで取得項目を決める、③listやfirstOptionで実行、という流れ
– 結合は、メソッドチェーンとfor式で発行するSQLが異なる
// select ・・・ from (select * from A) s1 inner join (select * from B) s2
on ・・・A.innerJoin(B).on(・・・)
// select ・・・ from A s1, B s2 where (s1.column = s2.column) and ・・for {
t1 <- A
t2 <- B
if ・・・} yield ・・・
28
留意事項
• Play2のEvolutionsは実践には厳しい
– 1.sql、2.sql・・・の管理はプログラマがやる
– ミスした場合、エボリューション用のテーブルを元に戻すという操作を手動でやらないといけない
– ある程度の規模になるとメンテが困難
29
Slickの今後
30
次期バージョン2.1
• 使いやすさの向上
– APIの改善
– ドキュメントの充実
• Play2.4からデフォルトのO/Rマッパ(予定)
• Direct Embedding
– ケースクラスにアノテーションを付与• スキーマ定義が不要になる
– 暗黙の型変換の代わりに、マクロを使用• 実装するコード量が減る
– 現在は実験的な機能として提供• 今後に期待