30
1 Slick入門 Takako Shimamoto BizReach, Inc.

Slick入門

Embed Size (px)

Citation preview

Page 1: Slick入門

1

Slick入門

Takako Shimamoto

BizReach, Inc.

Page 2: Slick入門

2

アジェンダ

• Scalaの代表的なORM

• Slickの基本的なお話

– スキーマ定義

– クエリ

• SQLを直接書く方法

– Plain SQL

– その他の選択肢

• Play2 + Slickで使うには

– 設定

– コネクションまわり

• プロジェクトでの留意点

Page 3: Slick入門

3

Scalaで使えるORM

• Slick(旧ScalaQuery)

– タイプセーフなDSLや、SQLも記述可能

– 極力Scalaのコレクションと同じように扱えるよう設計されている

• Squeryl

– SQLは使用せず、DSLでクエリを記述する

– コンセプトは、できるだけシンプルに

val query =

from(table)

(t => where(t.id === id) select(t))

Page 4: Slick入門

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)

} *)

Page 5: Slick入門

5

他にも、Activate、ScalikeJDBC、SORM など・・・

本日はTypesafe社お墨付きの

Slickについてお話します

Page 6: Slick入門

6

Slickのきほん

Page 7: 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のイメージ

Page 8: Slick入門

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))

Page 9: Slick入門

9

実はジェネレータがあるんですよ

Page 10: Slick入門

10

コードジェネレータ

• sbtのタスクとして実行

• 生成するファイル

– Tables.scala

• 生成されるもの

– テーブル定義

– マッピングするケースクラス

– TableQuery

• クエリで使用

– 全テーブルのDDL

– GetResult(ResultSetからケースクラスに変換)• Plain SQLで使用

Page 11: Slick入門

11

コードジェネレータ

• ジェネレータはカスタマイズ可能

– SourceCodeGeneratorを拡張する形

– ドキュメントがないので拡張ポイントがわかりにくい

– slick.model.codegenパッケージ配下を見るとよい

• BizReachではカスタマイズして使っている

– 基本的なCRUD機能

– シールドクラスとカラムのマッピング

– 楽観的排他による更新機能

Page 12: Slick入門

12

これでクエリが書けます

Page 13: Slick入門

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

Page 14: Slick入門

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を呼ぶとバインド変数になる

Page 15: Slick入門

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のイメージ

Page 16: Slick入門

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

Page 17: Slick入門

17

他にもいろいろ

• ソート、グルーピング、ページング など

• 内部結合、外部結合も可能

• slick-referenceをGitHubに公開中

https://github.com/bizreach/slick-reference

Page 18: Slick入門

18

タイプセーフなクエリで書けないときは?

Page 19: Slick入門

19

Plain SQL

• SQLを文字列リテラルで書く

• 局所的に使うには便利

• ただ、複雑なSQLの代替として使うには厳しい

– リファクタリングがやりにくい

– 動的なSQLを組み立てるためには文字列処理が必要

sql"""SELECT ID + 1

FROM ISSUE_ID

WHERE USER_NAME = $owner FOR UPDATE

""".as[Int].firstOption

Page 20: Slick入門

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

に流して動作確認できる外部ファイル化することも可能

Page 21: Slick入門

21

Play2でSlickを使うには

Page 22: Slick入門

22

play-slick

• Play2でSlickを使う上で、面倒なことをいろいろやってくれるプラグイン

– Play2の設定から自動的にSlickのDatabase.forDataSourceを呼び出してくれる

– Slickのセッション管理を自動的にやってくれる

• withSessionやwithTransactionを呼び出してくれる

– Play2のリクエストとSlickのセッションの両方を持ち合わせたDBSessionRequestが使える

SlickがPlay2に組み込まれるのと同時にこのプラグインも統合される予定

Page 23: Slick入門

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))

}

Page 24: Slick入門

24

コネクションプール

• Slickはコネクションプールの実装を持っていない

• Play2と組み合わせて使うならBoneCP

• ただし、有名なリーク問題が未だにある

– https://bugs.launchpad.net/bonecp/+bug/999114

• 対処方法は

– BoneCPのバージョンを固定

– maxConnectionAgeを0にする

• 現在は、HikariCPをお試し中

Page 25: Slick入門

25

プロジェクトで使う上で注意することは?

Page 26: Slick入門

26

留意事項

• アグレッシブな機能追加/削除

– Scala全体の傾向(Slickも例外ではない)

– 特にメジャーアップにはマイグレーションが必要

– 常にキャッチアップして対応していく必要がある

• マニュアルがあまり豊富でない

– 本家サイトには書いてないが、できることが色々ある

– 慣れるまでに多少の時間が必要

– 充実させようとしている動きはある

– BizReachで公開しているリファレンスをぜひ活用を

Page 27: Slick入門

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 ・・・

Page 28: Slick入門

28

留意事項

• Play2のEvolutionsは実践には厳しい

– 1.sql、2.sql・・・の管理はプログラマがやる

– ミスした場合、エボリューション用のテーブルを元に戻すという操作を手動でやらないといけない

– ある程度の規模になるとメンテが困難

Page 29: Slick入門

29

Slickの今後

Page 30: Slick入門

30

次期バージョン2.1

• 使いやすさの向上

– APIの改善

– ドキュメントの充実

• Play2.4からデフォルトのO/Rマッパ(予定)

• Direct Embedding

– ケースクラスにアノテーションを付与• スキーマ定義が不要になる

– 暗黙の型変換の代わりに、マクロを使用• 実装するコード量が減る

– 現在は実験的な機能として提供• 今後に期待