scala slick 方法我目前无法理解

Posted

技术标签:

【中文标题】scala slick 方法我目前无法理解【英文标题】:scala slick method I can not understand so far 【发布时间】:2012-12-04 02:34:56 【问题描述】:

我试图了解一些 Slick 作品及其要求。

这里是一个例子:

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

谁能解释我这里*方法的目的是什么,&lt;&gt;是什么,为什么unapply?什么是 Projection - 方法 ~' 返回 Projection2 的实例?

【问题讨论】:

【参考方案1】:

[更新] - for理解添加(又一个)解释

    * 方法:

    这将返回 默认投影 - 这就是您描述的方式:

    '我通常感兴趣的所有列(或计算值)'。

    您的表格可以有多个字段;你只需要一个子集 您的默认投影。默认投影必须与类型匹配 表的参数。

    让我们一次一个。没有&lt;&gt; 的东西,只有*

    // 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 的类型是什么?它是一个Query,投影为(String, Int)。 调用时,它根据投影返回 (String, Int) 元组中的 List

     val result: List[(String, Int)] = q.list
    

    在这种情况下,您已在 yield 子句中定义了所需的投影 for 的理解。

    现在关于&lt;&gt;Bar.unapply

    这提供了所谓的映射投影

    到目前为止,我们已经了解了 slick 如何让您在 Scala 中表达查询 返回列的投影(或计算值);所以在执行的时候 这些查询您必须将查询的结果行视为Scala 元组。 元组的类型将与定义的 Projection 匹配(由您的 for 理解与前面的示例一样,默认为 * 投影)。 这就是field1 ~ field2 返回Projection2[A, B] 的投影的原因,其中 Afield1 的类型,Bfield2 的类型。

    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.
    

    这是如何工作的? &lt;&gt; 进行投影 Projection2[Int, String] 和 返回Bar 类型的映射投影。两个论点Bar, Bar.unapply _ 告诉 slick 这个(Int, String) 投影必须如何映射到案例类。

    这是一个双向映射; Bar 是案例类构造函数,所以这就是 从(id: Int, name: String)Bar 所需的信息。和unapply 如果你猜对了,那就是相反的。

    unapply 来自哪里?这是可用于的标准 Scala 方法 任何普通案例类 - 只需定义 Bar 即可为您提供 Bar.unapply 是一个提取器,可用于取回idname Bar 是用:

    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 的类型是Query,其中映射 投影到Baz。 调用时,它会返回 ListBaz 对象:

     val result: List[Baz] = q1.list
    

    最后,顺便说一句,.? 提供 Option Lifting - Scala 方式 处理可能不是的值。

     (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 如何使用 for 理解的评论:

    不知何故,单子总是设法出现并要求 成为解释的一部分...

    For 推导不仅仅特定于集合。 它们可以用于任何类型的 Monad,并且集合是 只是 Scala 中可用的多种 monad 类型之一。

    但由于集合很熟悉,它们是一个良好的开端 求解释:

    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 理解是 方法(可能是嵌套的)方法调用:上面的代码 (或多或少)相当于:

    ns.filter(i => i*i % 2 == 0).map(i => i*i)
    

    基本上,filtermapflatMap 的任何内容 方法(换句话说,一个Monad)可以用在一个 for 理解代替 ns。一个很好的例子 是Option monad。这是前面的例子 其中相同的for 语句适用于两者 List 以及 Option monads:

    // (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 中,查询是一元的——它们只是对象 mapflatMapfilter 方法。所以for理解 (在* 方法的解释中显示)只是转换为:

    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
    

    如您所见,flatMapmapfilter 用于 通过Query(Bars)的重复变换生成Query 每次调用 filtermap。如果是 集合这些方法实际上迭代和过滤集合 但在 Slick 中,它们用于生成 SQL。更多细节在这里: How does Scala Slick translate Scala code into JDBC?

【讨论】:

在'1'解释块中:'val q = '是 WrappingQuery 并不明显,它在阅读代码时看起来像一个 List。它怎么可能转换为 Query..? (我仍在尝试您的解释以了解它是如何工作的。谢谢您!) @ses - 添加了一个(略长的)解释......另外,看看***.com/questions/13454347/monads-with-java-8/… - 我意识到它的内容几乎相同。 注意那些遇到神秘编译错误的人,使用 foo.?对于 Option[T] 列,否则您将难以阅读类型不匹配。谢谢,法伊兹! 这是一个很好的答案......如果它可以针对 Slick 3.0 进行更新,那就太好了【参考方案2】:

由于没有其他人回答,这可能有助于您入门。我不太了解 Slick。

来自Slick documentation:

提升嵌入:

每个表都需要一个包含默认投影的 * 方法。这 描述返回行时返回的内容(以 表对象)来自查询。 Slick 的 * 投影不必 匹配数据库中的那个。您可以添加新列(例如 计算值)或根据需要省略某些列。非升降型 对应于 * 投影作为类型参数给出 桌子。对于简单的非映射表,这将是单列 类型或列类型的元组。

换句话说,slick 需要知道如何处理从数据库返回的行。您定义的方法使用它们的解析器组合器函数将您的列定义组合成可以在行上使用的东西。

【讨论】:

确定。和 Projection 只是列的表示.. 像:final class Projection2[T1,T2]( override val _1: Column[T1], override val _2: Column[T2] ) extends Tuple2(_1,_2) with Projection[( T1,T2)] .. 现在.. 怎么会:Bar 有 'unapply' 方法? 啊哈.. - 所有案例类都实现了 Product 特征,而 unapply 是 Product 的方法。魔术。

以上是关于scala slick 方法我目前无法理解的主要内容,如果未能解决你的问题,请参考以下文章

Scala slick 2.0 updateAll 等同于 insertALL?

Scala Slick 无法将查询与计数联合起来

Scala 用于理解 Slick Query

Scala Play Framework Slick 会话不起作用

无法从“react-slick”解析“SimpleSlider.js”

在 Scala Slick 中实现类实例成员修改的最佳方法?