到目前为止我还无法理解的scala slick方法


89

我尝试了解一些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 _)
}

有人可以解释一下*这里方法的目的是什么<>,为什么unapply?什么是Projection-method ~返回的实例Projection2

Answers:


198

[更新] - 添加(还另外)关于for理解的解释

  1. *方法:

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

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

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

    让我们一次来一个。没有这些<>东西,只有*

    // 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)。当被调用时,它返回一个List(String, Int)元组按投影。

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

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

  2. 现在大约<>Bar.unapply

    这提供了所谓的“ 映射投影”

    到目前为止,我们已经看到了如何使用slick在Scala中表达查询以返回(或计算值)的投影;执行这些查询时,那么你必须考虑的结果行的查询作为Scala的元组。元组的类型将与定义的Projection相匹配(根据您 for在上一个示例中的理解,由默认*投影定义)。这就是为什么field1 ~ field2返回的投影Projection2[A, B]这里 A是类型field1B是的类型field2

    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]并返回该类型的映射投影Bar。这两个参数Bar, Bar.unapply _ 说明了如何将此(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
  3. 最后,顺便说一句,.?提供了“ 选项提升 ”-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]
  4. 针对有关Slick如何使用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 comprehension是方法(可能是嵌套的)方法调用的语法糖:上面的代码(或多或少)等效于:

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

    基本上,任何与filtermapflatMap 的方法(换言之,一个单子)可以以用于 for理解在适当位置的nsOption monad是一个很好的例子。这是前面的示例,其中相同的for语句在monad ListOptionmonad 上均起作用 :

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

    在油滑,查询是一元-它们与刚对象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通过反复改造Query(Bars) 与每个调用filtermap。对于集合,这些方法实际上会迭代和过滤集合,但是在Slick中,它们用于生成SQL。此处有更多详细信息: Scala Slick如何将Scala代码转换为JDBC?


在'1'解释块中:'val q ='不是WrappingQuery并不明显,在读取代码时它看起来像List <Projection2>。它如何转换为Query ..?(我仍在用您的解释来理解它的工作原理。谢谢您!)
ses

@ses-添加了对此的(略长的)解释...另外,请查看stackoverflow.com/questions/13454347/monads-with-java-8/…-我意识到这几乎是相同的内容。
法伊兹2012年

请注意那些遇到神秘的编译错误的人,请使用foo。对于Option [T]列,否则您将难以阅读类型不匹配。谢谢,法兹!
sventechie

1
这是一个很好的答案...虽然可以为Slick 3.0更新
Ixx

6

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

Slick文档中

解除嵌入:

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

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


ook 而Projection只是列的表示形式。 T1,T2)] {..
ses

现在..那怎么回事:Bar有'unapply'方法?
ses

2
Aha ..-所有案例类都实现了Product特质,而Unapply是Product的方法。魔法。
ses 2012年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.