The *
Metode:
Ini mengembalikan proyeksi default - seperti yang Anda gambarkan:
'semua kolom (atau nilai yang dihitung) yang biasanya saya minati'.
Tabel Anda dapat memiliki beberapa bidang; Anda hanya memerlukan subset untuk proyeksi default Anda. Proyeksi default harus sesuai dengan jenis parameter tabel.
Mari kita lakukan satu per satu. Tanpa <>
barang, hanya *
:
object Bars extends Table[(Int, String)]("bar") {
def id = column[Int]("id", O.PrimaryKey, O.AutoInc)
def name = column[String]("name")
def * = id ~ name
}
Hanya definisi tabel seperti itu yang akan memungkinkan Anda membuat kueri seperti:
implicit val session: Session =
val result = Query(Bars).list
proyeksi default dari (Int, String)
prospek ke List[(Int, String)]
untuk kueri sederhana seperti ini.
val q =
for (b <- Bars if b.id === 42)
yield (b.name ~ 1)
Apa jenisnya q
? Ini adalah Query
dengan proyeksi (String, Int)
. Saat dipanggil, ia mengembalikan a List
dari (String, Int)
tupel sesuai proyeksi.
val result: List[(String, Int)] = q.list
Dalam hal ini, Anda telah menentukan proyeksi yang Anda inginkan dalam yield
klausa for
pemahaman.
Sekarang tentang <>
dan Bar.unapply
.
Ini memberikan apa yang disebut Proyeksi yang Dipetakan .
Sejauh ini kita telah melihat bagaimana slick memungkinkan Anda untuk mengekspresikan kueri di Scala yang mengembalikan proyeksi kolom (atau nilai yang dihitung); Jadi saat menjalankan kueri ini, Anda harus memikirkan baris hasil kueri sebagai tupel Scala . Jenis tupel akan cocok dengan Proyeksi yang ditentukan (oleh for
pemahaman Anda
seperti pada contoh sebelumnya, dari *
proyeksi default ). Inilah sebabnya mengapa field1 ~ field2
mengembalikan proyeksi di Projection2[A, B]
mana
A
jenis field1
dan B
jenisnya field2
.
q.list.map {
case (name, n) =>
}
Queury(Bars).list.map {
case (id, name) =>
}
Kita berurusan dengan tupel, yang mungkin merepotkan jika kita memiliki terlalu banyak kolom. Kami ingin menganggap hasil bukan sebagai TupleN
objek dengan bidang bernama.
(id ~ name)
case class Bar(id: Int, name: String) // For now, using a plain Int instead
(id ~ name <> (Bar, Bar.unapply _))
Query(Bars).list.map ( b.name )
Bagaimana cara kerjanya? <>
mengambil proyeksi Projection2[Int, String]
dan mengembalikan proyeksi yang dipetakan pada tipe Bar
. Kedua argumen tersebut Bar, Bar.unapply _
menjelaskan secara apik bagaimana (Int, String)
proyeksi ini harus dipetakan ke kelas kasus.
Ini adalah pemetaan dua arah; Bar
adalah konstruktor kelas kasus, jadi itulah informasi yang diperlukan untuk beralih dari (id: Int, name: String)
ke a Bar
. Dan unapply
jika Anda sudah dapat menebaknya, itu sebaliknya.
Dari mana unapply
asalnya Ini adalah metode Scala standar yang tersedia untuk semua kelas kasus biasa - hanya dengan mendefinisikan yang Bar
memberi Anda Bar.unapply
sebuah ekstraktor yang dapat digunakan untuk mendapatkan kembali id
dan name
yang
Bar
telah dibangun dengan:
val bar1 = Bar(1, "one")
val Bar(id, name) = bar1
val bars: List[Bar] =
val barNames = bars.map {
case Bar(_, name) => name
}
val x = Bar.unapply(bar1)
Jadi proyeksi default Anda dapat dipetakan ke kelas kasus yang paling ingin Anda gunakan:
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 _)
}
Atau Anda bahkan dapat memilikinya per kueri:
case class Baz(name: String, num: Int)
val q1 =
for (b <- Bars if b.id === 42)
yield (b.name ~ 1 <> (Baz, Baz.unapply _))
Di sini tipe q1
adalah a Query
dengan proyeksi yang dipetakan ke Baz
. Saat dipanggil, ini mengembalikan a List
dari Baz
objek:
val result: List[Baz] = q1.list
Terakhir, sebagai tambahan, .?
penawaran Option Lifting - cara Scala menangani nilai-nilai yang mungkin tidak.
(id ~ name)
(id.? ~ name)
Yang mana, sebagai penutup, akan bekerja dengan baik dengan definisi asli Anda tentang Bar
:
case class Bar(id: Option[Int] = None, name: String)
val q0 =
for (b <- Bars if b.id === 42)
yield (b.id.? ~ b.name <> (Bar, Bar.unapply _))
q0.list
Menanggapi komentar tentang bagaimana Slick menggunakan for
pemahaman:
Entah bagaimana, monad selalu muncul dan menuntut untuk menjadi bagian dari penjelasan ...
Untuk pemahaman tidak khusus untuk koleksi saja. Mereka dapat digunakan pada semua jenis Monad , dan koleksinya hanyalah salah satu dari banyak jenis monad yang tersedia di Scala.
Tetapi karena koleksi sudah familiar, mereka menjadi titik awal yang baik untuk penjelasan:
val ns = 1 to 100 toList;
val result =
for { i <- ns if i*i % 2 == 0 }
yield (i*i)
Dalam Scala, pemahaman for adalah gula sintaksis untuk panggilan metode (mungkin bersarang): Kode di atas (lebih atau kurang) setara dengan:
ns.filter(i => i*i % 2 == 0).map(i => i*i)
Pada dasarnya, apa-apa dengan filter
, map
, flatMap
metode (dengan kata lain, sebuah Monad ) dapat digunakan dalam
for
pemahaman di tempat ns
. Contoh yang bagus adalah Option monad . Berikut contoh sebelumnya di mana sama for
pernyataan bekerja pada kedua
List
serta Option
monads:
val result =
for {
i <- ns
i2 <- Some(i*i)
if i2 % 2 == 0
} yield i2
def evenSqr(n: Int) = {
val sqr = n*n
if (sqr % 2 == 0) Some (sqr)
else None
}
result =
for {
i <- ns
i2 <- evenSqr(i)
} yield i2
Pada contoh terakhir, transformasi mungkin akan terlihat seperti ini:
val result =
ns.flatMap(i => Some(i*i)).filter(i2 => i2 %2 ==0)
result =
ns.flatMap(i => evenSqr(i))
Di Slick, kueri bersifat monadik - mereka hanyalah objek dengan metode map
, flatMap
dan filter
. Jadi for
pemahaman (ditunjukkan dalam penjelasan *
metode) hanya diterjemahkan menjadi:
val q =
Query(Bars).filter(b => b.id === 42).map(b => b.name ~ 1)
val r: List[(String, Int)] = q.list
Seperti yang Anda lihat, flatMap
, map
dan filter
digunakan untuk menghasilkan Query
oleh transformasi berulang Query(Bars)
dengan setiap permintaan dari filter
dan map
. Dalam kasus koleksi, metode ini sebenarnya mengulangi dan memfilter koleksi tetapi di Slick mereka digunakan untuk menghasilkan SQL. Lebih jelasnya di sini:
Bagaimana Scala Slick menerjemahkan kode Scala ke JDBC?