MySQLのクエリにSlickを使用する方法を学ぼうとしています。次のタイプのクエリが1つのVisitオブジェクトを取得するために機能しています。
Q.query[(Int,Int), Visit]("""
select * from visit where vistor = ? and location_code = ?
""").firstOption(visitorId,locationCode)
私が知りたいのは、上記のクエリを実行して、ロケーションのコレクションのList [Visit]を取得するように変更する方法です。次のようなものです。
val locationCodes = List("loc1","loc2","loc3"...)
Q.query[(Int,Int,List[String]), Visit]("""
select * from visit where vistor = ? and location_code in (?,?,?...)
""").list(visitorId,locationCodes)
これはSlickで可能ですか?
他の回答が示唆しているように、これは静的クエリで行うのは面倒です。静的クエリインターフェイスでは、バインドパラメータをProduct
として記述する必要があります。 (Int, Int, String*)
は無効なscalaであり、(Int,Int,List[String])
を使用するには、いくつかのkludgeも必要です。さらに、locationCodes.size
が常にクエリ内の(?, ?...)
の数と等しいことを確認する必要があることは、脆弱です。
実際には、それほど問題ではありません。代わりにクエリモナドを使用する必要があるためです。これは、タイプセーフであり、Slickを使用するための推奨される方法です。
val visitorId: Int = // whatever
val locationCodes = List("loc1","loc2","loc3"...)
// your query, with bind params.
val q = for {
v <- Visits
if v.visitor is visitorId.bind
if v.location_code inSetBind locationCodes
} yield v
// have a look at the generated query.
println(q.selectStatement)
// run the query
q.list
これは、テーブルが次のように設定されていることを前提としています。
case class Visitor(visitor: Int, ... location_code: String)
object Visitors extends Table[Visitor]("visitor") {
def visitor = column[Int]("visitor")
def location_code = column[String]("location_code")
// .. etc
def * = visitor ~ .. ~ location_code <> (Visitor, Visitor.unapply _)
}
クエリは常にメソッドでラップできることに注意してください。
def byIdAndLocations(visitorId: Int, locationCodes: List[String]) =
for {
v <- Visits
if v.visitor is visitorId.bind
if v.location_code inSetBind locationCodes
} yield v
}
byIdAndLocations(visitorId, List("loc1", "loc2", ..)) list
_StaticQuery object
_(Q
)は、query
メソッドの型パラメーターを使用して一種のセッターを作成し、クエリ文字列にパラメーターを暗黙的に設定することを期待しているため、機能しませんオブジェクト(タイプ _scala.slick.jdbc.SetParameter[T]
_ )。
_SetParameter[T]
_の役割は、クエリパラメータをタイプT
の値に設定することです。ここで、必要なタイプは_query[...]
_タイプパラメータから取得されます。
一般的なA
に対して_T = List[A]
_に定義されたそのようなオブジェクトはないことがわかり、実際にはsqlクエリをパラメーターの動的リストで記述できないため、これは賢明な選択のようですIN (?, ?, ?,...)
句
私は次のコードを通じてそのような暗黙の値を提供することによって実験をしました
_import scala.slick.jdbc.{SetParameter, StaticQuery => Q}
def seqParam[A](implicit pconv: SetParameter[A]): SetParameter[Seq[A]] = SetParameter {
case (seq, pp) =>
for (a <- seq) {
pconv.apply(a, pp)
}
}
implicit val listSP: SetParameter[List[String]] = seqParam[String]
_
これがスコープ内にあれば、コードを実行できるはずです
_val locationCodes = List("loc1","loc2","loc3"...)
Q.query[(Int,Int,List[String]), Visit]("""
select * from visit where vistor = ? and location_code in (?,?,?...)
""").list(visitorId,locationCodes)
_
ただし、locationCodes
サイズがIN
句の_?
_の数と同じであることを常に手動で保証する必要があります
結局のところ、シーケンスタイプを一般化するために、マクロを使用してより明確な回避策を作成できると思います。しかし、シーケンスサイズの動的な性質に関する前述の問題を考えると、フレームワークが賢明な選択になるかどうかはわかりません。
次のようにして自動的にin句を生成できます。
def find(id: List[Long])(implicit options: QueryOptions) = {
val in = ("?," * id.size).dropRight(1)
Q.query[List[Long], FullCard](s"""
select
o.id, o.name
from
organization o
where
o.id in ($in)
limit
?
offset
?
""").list(id ::: options.limits)
}
そして暗黙のSetParameterを pagoda_5bは言う として使用します
def seqParam[A](implicit pconv: SetParameter[A]): SetParameter[Seq[A]] = SetParameter {
case (seq, pp) =>
for (a <- seq) {
pconv.apply(a, pp)
}
}
implicit def setLongList = seqParam[Long]
複雑なクエリがあり、上記のfor理解がオプションでない場合、Slick 3で次のようなことを行うことができますが、SQLインジェクションを防ぐために、リストクエリパラメータのデータを自分で検証する必要があります。
val locationCodes = "'" + List("loc1","loc2","loc3").mkString("','") + "'"
sql"""
select * from visit where visitor = $visitor
and location_code in (#$locationCodes)
"""
変数参照の前の#は、型の検証を無効にし、リストクエリパラメーターの暗黙的な変換のための関数を提供せずにこれを解決できるようにします。