Метод 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
постижения
*
метод:Это возвращает проекцию по умолчанию - как вы описываете:
"все столбцы (или вычисленные значения), которые меня обычно интересуют".
Ваша таблица может иметь несколько полей; вам нужно только подмножество для вашей проекции по умолчанию. Проекция по умолчанию должна соответствовать параметрам типа таблицы.
Давайте возьмем это по одному. Без
<>
вещи, просто*
:// 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
понимание.Теперь о
<>
а также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
Наконец, в стороне,
.?
предлагает 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
постижения:Так или иначе, монады всегда обнаруживаются и требуют быть частью объяснения...
Для понимания не характерны только для коллекций. Их можно использовать в любой монаде, а коллекции - это только один из множества типов монад, доступных в 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 должен знать, как обращаться со строкой, возвращенной из базы данных. Метод, который вы определили, использует их функции комбинатора синтаксического анализатора, чтобы объединить ваши определения столбцов во что-то, что можно использовать в строке.