斯卡拉光滑的方法到目前为止我不明白
我试图理解一些油滑的作品和它需要的东西。
这里是一个例子:
package models case class Bar(id: Option[Int] = None, name: String) object Bars extends Table[Bar]("bar") { def id = column[Int]("id", O.PrimaryKey, O.AutoInc) // This is the primary key column def name = column[String]("name") // Every table needs a * projection with the same type as the table's type parameter def * = id.? ~ name <>(Bar, Bar.unapply _) }
有人可以解释一下这个方法的目的是什么,什么是<>
,为什么不应用? 什么是Projection – ~
'返回Projection2
的实例?
[更新] – 增加(又一个)解释理解
-
*
方法:这将返回默认的投影 – 这是你如何描述:
'我通常感兴趣的所有列(或计算值)“。
你的桌子可以有几个字段; 您只需要一个默认投影的子集。 默认的投影必须匹配表的types参数。
让我们一次一个。 没有
<>
东西,只是*
:// First take: Only the Table Defintion, no case class: object Bars extends Table[(Int, String)]("bar") { def id = column[Int]("id", O.PrimaryKey, O.AutoInc) def name = column[String]("name") def * = id ~ name // Note: Just a simple projection, not using .? etc } // Note that the case class 'Bar' is not to be found. This is // an example without it (with only the table definition)
只是像这样的表定义会让你的查询如下所示:
implicit val session: Session = // ... a db session obtained from somewhere // A simple select-all: val result = Query(Bars).list // result is a List[(Int, String)]
(Int, String)
的默认投影导致一个List[(Int, String)]
对于这些简单的查询。// SELECT b.name, 1 FROM bars b WHERE b.id = 42; val q = for (b <- Bars if b.id === 42) yield (b.name ~ 1) // yield (b.name, 1) // this is also allowed: // tuples are lifted to the equivalent projection.
q
的types是什么? 这是一个带有投影的Query
(String, Int)
。 当被调用时,它根据投影返回一个List
(String, Int)
元组。val result: List[(String, Int)] = q.list
在这种情况下,你已经在
for
理解的yield
子句中定义了你想要的投影。 -
现在关于
<>
和Bar.unapply
。这提供了所谓的映射投影 。
到目前为止,我们已经看到了slick是如何允许你在Scala中表示查询,返回列 (或计算值)的投影 。 所以当执行这些查询时, 你必须把查询的结果行 看作一个Scala元组 。 元组的types将与定义的投影相匹配(在前面的例子中,由默认的
*
投影for
理解)。 这就是为什么field1 ~ field2
返回Projection2[A, B]
的Projection2[A, B]
其中A
是field1
的types,B
是field2
的types。q.list.map { case (name, n) => // do something with name:String and n:Int } Queury(Bars).list.map { case (id, name) => // do something with id:Int and name:String }
我们正在处理元组,如果列数太多,这可能会很麻烦。 我们想把结果看作是
TupleN
,而不是一些带有命名字段的对象。(id ~ name) // A projection // Assuming you have a Bar case class: case class Bar(id: Int, name: String) // For now, using a plain Int instead // of Option[Int] - for simplicity (id ~ name <> (Bar, Bar.unapply _)) // A MAPPED projection // Which lets you do: Query(Bars).list.map ( b.name ) // instead of // Query(Bars).list.map { case (_, name) => name } // Note that I use list.map instead of mapResult just for explanation's sake.
这个怎么用? 使用投影
Projection2[Int, String]
并返回typesBar
上的映射投影。 这两个参数Bar, Bar.unapply _
tellBar, Bar.unapply _
如何将这个(Int, String)
投影映射到一个case类。这是一个双向映射;
Bar
是case类的构造函数,所以这是从(id: Int, name: String)
到一个Bar
。 而且,如果你猜对了,反之亦然。不适用从哪里来? 这是一个标准的Scala方法可用于任何普通的案例类 – 只是定义
Bar
给你一个Bar.unapply
这是一个提取器 ,可用于取回Bar
的id
和name
build:val bar1 = Bar(1, "one") // later val Bar(id, name) = bar1 // id will be an Int bound to 1, // name a String bound to "one" // Or in pattern matching val bars: List[Bar] = // gotten from somewhere val barNames = bars.map { case Bar(_, name) => name } val x = Bar.unapply(bar1) // x is an Option[(String, Int)]
因此,您的默认投影可以映射到您最期望使用的案例类:
object Bars extends Table[Bar]("bar") { def id = column[Int]("id", O.PrimaryKey, O.AutoInc) def name = column[String]("name") def * = id ~ name <>(Bar, Bar.unapply _) }
或者你甚至可以有每个查询:
case class Baz(name: String, num: Int) // SELECT b.name, 1 FROM bars b WHERE b.id = 42; val q1 = for (b <- Bars if b.id === 42) yield (b.name ~ 1 <> (Baz, Baz.unapply _))
这里
q1
的types是一个带有映射到Baz
投影的Query
。 当被调用时,它返回一个Baz
对象List
:val result: List[Baz] = q1.list
-
最后,作为一个旁白, 提供选项提升 – 斯卡拉处理值不可能的方式。
(id ~ name) // Projection2[Int, String] // this is just for illustration (id.? ~ name) // Projection2[Option[Int], String]
其中,包裹,将与您原来的
Bar
定义很好地工作:case class Bar(id: Option[Int] = None, name: String) // SELECT b.id, b.name FROM bars b WHERE b.id = 42; val q0 = for (b <- Bars if b.id === 42) yield (b.id.? ~ b.name <> (Bar, Bar.unapply _)) q0.list // returns a List[Bar]
-
回应评论Slick如何理解:
不知何故,monads总是设法出现并要求成为解释的一部分。
理解不仅限于集合。 它们可以用在任何一种Monad上 ,集合只是Scala中可用的monadtypes中的一种。
但是,由于藏品很熟悉,它们为解释提供了一个很好的起点:
val ns = 1 to 100 toList; // Lists for familiarity val result = for { i <- ns if i*i % 2 == 0 } yield (i*i) // result is a List[Int], List(4, 16, 36, ...)
在Scala中,for理解是方法调用的语法糖(可能是嵌套的)方法调用:上面的代码(或多或less)等同于:
ns.filter(i => i*i % 2 == 0).map(i => i*i)
基本上,任何带有
filter
,map
,flatMap
方法的东西(换句话说,一个Monad )都可以用来理解ns
代替。 Option monad就是一个很好的例子。 这里是前面的例子,其中相同for
语句对List
以及Option
都起作用:// (1) val result = for { i <- ns // ns is a List monad i2 <- Some(i*i) // Some(i*i) is Option if i2 % 2 == 0 // filter } yield i2 // Slightly more contrived example: def evenSqr(n: Int) = { // return the square of a number val sqr = n*n // only when the square is even if (sqr % 2 == 0) Some (sqr) else None } // (2) result = for { i <- ns i2 <- evenSqr(i) // i2 may/maynot be defined for i! } yield i2
在最后一个例子中,转换可能看起来像这样:
// 1st example val result = ns.flatMap(i => Some(i*i)).filter(i2 => i2 %2 ==0) // Or for the 2nd example result = ns.flatMap(i => evenSqr(i))
在Slick中,查询是monadic – 它们只是
map
,flatMap
和filter
方法的对象。 所以理解(在*
方法的解释中显示)只是转化为:val q = Query(Bars).filter(b => b.id === 42).map(b => b.name ~ 1) // Type of q is Query[(String, Int)] val r: List[(String, Int)] = q.list // Actually run the query
正如你所看到的,
flatMap
,map
和filter
用于通过Query(Bars)
的重复转换来生成Query
,每次调用filter
和map
。 在集合的情况下,这些方法实际上迭代和过滤集合,但是在Slick中它们被用来生成SQL。 更多细节: Scala Slick如何将Scala代码转换成JDBC?
由于没有人回答,这可能有助于你开始。 我不太了解Slick。
从Slick文档 :
提升embedded:
每个表都需要一个包含默认投影的方法。 这描述了从查询中返回行(以表格对象的forms)时的结果。 Slick的*投影不一定要匹配数据库中的。 你可以添加新的列(例如计算值),或者只要你喜欢省略一些列。 对应于*投影的非提升types被作为表格的types参数给出。 对于简单的非映射表,这将是单列types或列types的元组。
换句话说,浮油需要知道如何处理从数据库返回的行。 您定义的方法使用它们的parsing器组合函数来将您的列定义合并到可以在一行中使用的东西。