Slick3野郎の備忘録1
ドキュメント類
SQLの実行方法あれやこれや
基本は
main.scala
do.run(<action>)
action部分
action.scala
//insert系
Users += User(3, "charles", "charles@example.com")
//update系
Users.filter { _.name.startsWith("alice") }.map(_.name).update("alice-updated")
//delete系
Users.filter{ _.name === "alice" }.delete
group byとかこんな感じ 参考にしたサイト
groupby.sql
SELECT s.club_id, s.classroom, count(*)
FROM Sutudents s
GROUP BY s.club_id, u.classroom
↓slickで書くと
slick.scala
val q3 = for {
((ci, cr), ss) <- Students.groupBy(s => (s.clubId, s.classroom))
} yield (ci, cr, ss.length)
val r3: List[(Option[Long], String, Int)] = q3.list()
stackoverflow リレーション状態の結果を取得する方法
joinしてGroupByするケース ドキュメント少なッ!
main.scala
// 入れ物の型を用意します
// parents, childs は別途modelが存在する想定です
case class ChildSummaryEntity(childsCount: Int)
case class ParentEntity(id: Option[Long], parentname: String, summary: Option[ChildSummaryEntity])
def getNestedEntityByGroup(parentId: Long): Future[Seq[ParentEntity]] = {
val sampleId: Long = 159
val a =
// where句
parents.filter( _.id === parentId ).flatMap(p =>
childs.filter( _.parentId === p.id ).map((p, _ )))
// gourp by節 ここで2つ指定するとselect句のPはTuple型になるので注意
.groupBy (u => (p._1.id, p._1.parentname))
// select句
.map { case (p, c) => (
p._1,
p._2,
c.map(_._1).length)} // select column
.result
db.run(a.transactionally).flatMap { r =>
Future.successful {
r.map {
// select句の項目を元にネストオブジェクトを作成する
case (i, n, s) => ParentEntity(i, n, Some(ChildSummaryEntity(s)) )
}
}
}
}
注意点
joinLeftなどで外部結合する場合、Left側はOption扱いになるので注意が必要。
LeftサイドのColumnを扱う場合はmap(.hoge)ではなくflatMap(.hoge)とする必要がある。
それはなぜか?
mapを使った場合、出力される生SQLを観察すると一目瞭然だが、射影に不要なcase文が混入する場合がある。
slick3におけるflatMapについての理解
生SQL
サービストレイトに実装する場合を前提に進めます
- 利用部分のサンプルはこちら
route.scala
trait FuckServiceRoute extends FuckService {
// (_.toJson)でJSON変換する時のフォーマット用implict
implicit val fuckFormat = jsonFormat3(FuckEntity)
trait FuckRoute extends FuckService {
val fuckRoute = pathPrefix("fuck") {
get {
complete(getFuckByRawSQL.map(_.toJson))
}
}
}
- 実行部分のTraitはこちら
transfer.scala
import models.FuckTransfer
trait FuckService extends FuckTable with FuckTransfer {
// マッピングはFuckTransfer.FuckEntity側でimplictで定義するのでここでは何も気にする必要なし
def getFuckByRawSQL() : Future[Seq[FuckEntity]]={
// 文字列補完をしてみるslick側でSQLインジェクション対応までやってくれます
val s = "fuck"
val a = sql"SELECT id, $s, created_at FROM fuck".as[FuckEntity]
db.run(a)
}
}
- トランスファーのTraitはこちら
model.scala
package models
import slick.jdbc.GetResult
import org.joda.time.DateTime
// 素のslickには、jodatimeのimplictがないので追加、これがないとDateTimeは利用できないぜ
import com.github.tototoshi.slick.MySQLJodaSupport._
trait FuckTransfer {
case class FuckEntity(userId: Long, fuck: String, createAt: DateTime)
//これだけで勝手にマッピングしてくれますが、順番大事 名前大事
implicit val getFuckEntityResult = GetResult(r => FuckEntity(r.<<, r.<<, r.<<))
}
トランザクション
transactionnally.scala
//トランザクション中で複数の処理を事項する場合
def getAcidTest(): Future[Unit] ={
db.run(
DBIO.seq(
// みかんを蜜柑に変えて値段を変更(Item丸ごと更新)
items.filter(_.id === 2).update(Item(2, "蜜柑", 250)),
// 蜜柑をミカンに変更(nameだけを更新)
items.filter(_.id === 2).map(_.name).update("ミカン"),
// idが2より大きい物のpriceを800に(複数レコードをまとめて更新)
items.filter(_.id > 2).map(_.price).update(800)
).transactionally
)
}
db.runで実行するactionは一つなので複数の処理が必要な場合はアクション合成する
※○!=trueなら処理するなど
つまり複数の処理を行う場合は全てアクション合成する必要がある
検索結果をもとに計算処理をし、生SQLを発行して結果を合成する時も同じ(逆に面倒くさいわっ!)
stack overflow
アクション合成
注意点
- ジェネリック系の雛型に出来るのは「case class」だけ
- slickのmodelはトレイト