Метод scala slick пока не могу понять

Я пытаюсь понять некоторые работы 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? а что такое проекция - метод ~возвращает экземпляр Projection2?

2 ответа

Решение

[ОБНОВЛЕНИЕ] - добавлено (еще одно) объяснение 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
    

    В этом случае вы определили нужную проекцию в yield пункт о for понимание.

  2. Теперь о <> а также Bar.unapply,

    Это обеспечивает то, что называется Mapped Projection.

    До сих пор мы видели, как Slick позволяет выражать запросы в Scala, которые возвращают проекцию столбцов (или вычисленных значений); Поэтому при выполнении этих запросов вы должны рассматривать результирующую строку запроса как кортеж Scala. Тип кортежа будет соответствовать проекции, которая определена (вашим for понимание, как в предыдущем примере, по умолчанию * проекция). Вот почему field1 ~ field2 возвращает проекцию Projection2[A, B] где A это тип field1 а также B это тип 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 это конструктор класса case, так что это информация, необходимая для перехода (id: Int, name: String) к Bar, А также unapply если вы догадались, это для обратного.

    Где же unapply родом из? Это стандартный метод Scala, доступный для любого обычного класса case - просто определение Bar дает вам Bar.unapply который является экстрактором, который может быть использован, чтобы вернуть id а также name что 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, При вызове возвращает List из Baz объекты:

     val result: List[Baz] = q1.list
    
  3. Наконец, в стороне, .? предлагает 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]
    
  4. В ответ на комментарий о том, как использует Slick for постижения:

    Так или иначе, монады всегда обнаруживаются и требуют быть частью объяснения...

    Для понимания не характерны только для коллекций. Их можно использовать в любой монаде, а коллекции - это только один из множества типов монад, доступных в Scala.

    Но поскольку коллекции знакомы, они являются хорошей отправной точкой для объяснения:

    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 a для понимания - синтаксический сахар для вызовов методов (возможно, вложенных): приведенный выше код (более или менее) эквивалентен:

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

    В основном, что-нибудь с filter, map, flatMap методы (другими словами, монады) могут быть использованы в for понимание вместо ns, Хорошим примером является монада Option. Вот предыдущий пример, где тот же 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 запросы монадические - это просто объекты с map, flatMap а также filter методы. Итак 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
    

    Как вы видете, flatMap, map а также filter используются для генерации Query повторным преобразованием Query(Bars) с каждым вызовом filter а также map, В случае коллекций эти методы фактически повторяют и фильтруют коллекцию, но в Slick они используются для генерации SQL. Подробнее здесь: Как Scala Slick переводит код Scala в JDBC?

Поскольку никто не ответил, это может помочь вам начать. Я не очень хорошо знаю Слика.

Из документации Slick:

Поднял вложение:

Для каждой таблицы требуется метод *, содержащий проекцию по умолчанию. Это описывает то, что вы получаете, когда возвращаете строки (в форме табличного объекта) из запроса. * Slick проекция не должна совпадать с проекцией в базе данных. Вы можете добавить новые столбцы (например, с вычисленными значениями) или опустить некоторые столбцы, как вам нравится. Неподнятый тип, соответствующий проекции *, задается в качестве параметра типа для таблицы. Для простых не отображенных таблиц это будет один тип столбца или кортеж типов столбцов.

Другими словами, slick должен знать, как обращаться со строкой, возвращенной из базы данных. Метод, который вы определили, использует их функции комбинатора синтаксического анализатора, чтобы объединить ваши определения столбцов во что-то, что можно использовать в строке.

Другие вопросы по тегам