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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/13906684/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-22 04:48:59  来源:igfitidea点击:

scala slick method I can not understand so far

scalaplayframework-2.0magic-methodsscalaqueryslick

提问by ses

I try to understand some Slick works and what it requires.

我试图了解一些 Slick 的作品以及它需要什么。

Here it an example:

这是一个例子:

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

Could somebody explain me what's the purpose of *method here, what is <>, why unapply? and what is Projection - method ~' returns the instance of Projection2?

有人可以解释我*这里方法的目的是什么,是什么<>,为什么unapply?什么是投影方法~'返回的实例Projection2

回答by Faiz

[UPDATE]- added (yet another) explanation on forcomprehensions

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

  1. The *method:

    This returns the default projection- which is how you describe:

    'all the columns (or computed values) I am usuallyinterested' in.

    Your table could have several fields; you only need a subset for your default projection. The default projection must match the type parameters of the table.

    Let's take it one at a time. Without the <>stuff, just the *:

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

    Just a table definition like that will let you make queries like:

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

    the default projection of (Int, String)leads to a List[(Int, String)]for simple queries such as these.

    // 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.
    

    What's the type of q? It is a Querywith the projection (String, Int). When invoked, it returns a Listof (String, Int)tuples as per the projection.

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

    In this case, you have defined the projection you want in the yieldclause of the forcomprehension.

  2. Now about <>and Bar.unapply.

    This provides what are called Mapped Projections.

    So far we've seen how slick allows you to express queries in Scala that return a projection of columns(or computed values); So when executing these queries you have to think of the result rowof a query as a Scala tuple. The type of the tuple will match the Projection that is defined (by your forcomprehension as in the previous example, of by the default *projection). This is why field1 ~ field2returns a projection of Projection2[A, B]where Ais the type of field1and Bis the type of 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 
    }
    

    We're dealing with tuples, which may be cumbersome if we have too many columns. We'd like to think of results not as TupleNbut rather some object with named fields.

    (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.
    

    How does this work? <>takes a projection Projection2[Int, String]and returns a mapped projection on the type Bar. The two arguments Bar, Bar.unapply _tell slick how this (Int, String)projection must be mapped to a case class.

    This is a two-way mapping; Baris the case class constructor, so that's the information needed to go from (id: Int, name: String)to a Bar. And unapplyif you've guessed it, is for the reverse.

    Where does unapplycome from? This is a standard Scala method available for any ordinary case class - just defining Bargives you a Bar.unapplywhich is an extractorthat can be used to get back the idand namethat the Barwas built with:

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

    So your default projection can be mapped to the case class you most expect to use:

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

    Or you can even have it per-query:

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

    Here the type of q1is a Querywith a mappedprojection to Baz. When invoked, it returns a Listof Bazobjects:

     val result: List[Baz] = q1.list
    
  3. Finally, as an aside, the .?offers Option Lifting- the Scala way of dealing with values that may not be.

     (id ~ name)   // Projection2[Int, String] // this is just for illustration
     (id.? ~ name) // Projection2[Option[Int], String]
    

    Which, wrapping up, will work nicely with your original definition of 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. In response to the comment on how Slick uses forcomprehensions:

    Somehow, monads always manage to show up and demand to be part of the explanation...

    For comprehensions are not specific to collections only. They may be used on any kind of Monad, and collections are just one of the many kinds of monad types available in Scala.

    But as collections are familiar, they make a good starting point for an explanation:

    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, ...)
    

    In Scala, a for comprehension is syntactic sugar for method (possibly nested) method calls: The above code is (more or less) equivalent to:

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

    Basically, anything with filter, map, flatMapmethods (in other words, a Monad) can be used in a forcomprehension in place of ns. A good example is the Option monad. Here's the previous example where the same forstatement works on both the Listas well as Optionmonads:

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

    In the last example, the transformation would perhaps look like this:

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

    In Slick, queries are monadic - they are just objects with the map, flatMapand filtermethods. So the forcomprehension (shown in the explanation of the *method) just translates to:

    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
    

    As you can see, flatMap, mapand filterare used to generate a Queryby the repeated transformation of Query(Bars)with each invocation of filterand map. In the case of collections these methods actually iterate and filter the collection but in Slick they are used to generate SQL. More details here: How does Scala Slick translate Scala code into JDBC?

  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)导致的默认投影为 a 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

    这提供了所谓的Mapped Projections

    到目前为止,我们已经看到 slick 如何允许您在 Scala 中表达返回(或计算值)投影的查询;执行这些查询时,那么你必须考虑的结果行的查询作为Scala的元组。元组的类型将与定义的 Projection 匹配(根据您 for在前面示例中的理解,默认*投影)。这就是为什么field1 ~ field2返回Projection2[A, B]where Ais the type offield1Bis the type of的投影的原因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 _告诉 slick(Int, String)必须如何将此投影映射到案例类。

    这是一个双向映射;Bar就是如此类的构造函数,所以这是从去所需要的信息(id: Int, name: String)Bar。而且unapply,如果你已经猜到了,是相反的。

    哪里unapply来的呢?这是一个可用于任何普通 case 类的标准 Scala 方法 - 只需定义Bar给你 a Bar.unapplywhich 是一个提取器,可用于取回id并且nameBar是用以下方法构建的:

    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是 a 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)
    

    基本上,任何带有filter, map,flatMap方法的东西(换句话说,一个Monad)都可以用在 for理解中来代替ns。一个很好的例子是Option monad。这是前面的示例,其中相同的for语句适用于 monadListOptionmonad:

    // (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 中,查询是一元的——它们只是带有map,flatMapfilter方法的对象。所以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?

回答by Dominic Bou-Samra

Since no one else has answered, this might help to get you started. I don't know Slick very well.

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

From the Slick documentation:

Slick 文档

Lifted Embedding:

Every table requires a * method contatining a default projection. This describes what you get back when you return rows (in the form of a table object) from a query. Slick's * projection does not have to match the one in the database. You can add new columns (e.g. with computed values) or omit some columns as you like. The non-lifted type corresponding to the * projection is given as a type parameter to Table. For simple, non-mapped tables, this will be a single column type or a tuple of column types.

提升嵌入:

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

In other words, slick needs to know how to deal with a row returned from the database. The method you defined uses their parser combinator functions to combine your column definitions into something that can be used on a row.

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