วิธี scala slick ฉันยังไม่เข้าใจ


89

ฉันพยายามทำความเข้าใจงานเนียนบางอย่างและสิ่งที่ต้องใช้

นี่คือตัวอย่าง:

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อะไร?

คำตอบ:


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)tuples ตามประมาณการ

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

    ในกรณีนี้คุณได้กำหนดการฉายภาพที่คุณต้องการในyieldประโยคของความforเข้าใจ

  2. ตอนนี้เกี่ยวกับ<>และBar.unapply.

    แห่งนี้มีสิ่งที่เรียกว่าการประมาณการแมป

    จนถึงตอนนี้เราได้เห็นแล้วว่า Slick ช่วยให้คุณแสดงข้อความค้นหาใน Scala ที่ส่งคืนการฉายภาพของคอลัมน์ (หรือค่าที่คำนวณได้) ได้อย่างไร ดังนั้นเมื่อมีการดำเนินการค้นหาเหล่านี้คุณต้องคิดของแถวผลของแบบสอบถามเป็น tuple ประเภทของทูเปิลจะตรงกับการฉายภาพที่กำหนดไว้ (ตาม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เป็นตัวสร้างคลาสเคสดังนั้นนั่นคือข้อมูลที่จำเป็นในการเปลี่ยนจาก(id: Int, name: String)เป็นBarไฟล์. และunapply ถ้าคุณเดาได้ก็คือการย้อนกลับ

    ในกรณีที่ไม่unapplyมาจากไหน? นี้เป็นวิธีที่ Scala มาตรฐานที่สามารถใช้ได้สำหรับการเรียนกรณีใด ๆ สามัญ - เพียงแค่กำหนด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. ในที่สุดนอกจาก.?นี้ข้อเสนอการยกตัวเลือก - วิธีการจัดการกับค่านิยมของสกาล่าที่อาจไม่เป็นเช่นนั้น

     (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ความเข้าใจ:

    อย่างไรก็ตาม monads มักจะปรากฏตัวและเรียกร้องให้เป็นส่วนหนึ่งของคำอธิบาย ...

    เพื่อความเข้าใจไม่ได้เจาะจงเฉพาะคอลเลกชันเท่านั้น อาจใช้กับMonadประเภทใดก็ได้และคอลเลกชันเป็นเพียงหนึ่งในประเภท Monad หลายประเภทที่มีอยู่ใน 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 for comp understandion คือน้ำตาลวากยสัมพันธ์สำหรับการเรียกเมธอดเมธอด (อาจซ้อนกัน): โค้ดด้านบน (มากหรือน้อย) เทียบเท่ากับ:

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

    โดยทั่วไปอะไรกับfilter, map, flatMap วิธีการ (ในคำอื่น ๆMonad ) สามารถนำมาใช้ใน ความเข้าใจในสถานที่ของfor nsตัวอย่างที่ดีคือmonad ตัวเลือก นี่คือตัวอย่างก่อนหน้านี้ที่เดียวกันforคำสั่งการทำงานทั้งบน Listเช่นเดียวกับ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
    

    ในตัวอย่างสุดท้ายการเปลี่ยนแปลงอาจมีลักษณะดังนี้:

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

    ในเนียนแบบสอบถามเป็นเอก - พวกเขาเป็นวัตถุเพียงกับ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 ได้อย่างไร


ในบล็อกคำอธิบาย '1': ไม่ชัดเจนว่า 'val q =' คือ WrappingQuery ดูเหมือนรายการ <Projection2> ในขณะที่อ่านโค้ด เป็นไปได้อย่างไรที่มันจะเปลี่ยนเป็นแบบสอบถาม .. ? (ฉันกำลังเล่นกับคำอธิบายของคุณเพื่อทำความเข้าใจว่ามันทำงานอย่างไรขอบคุณสำหรับสิ่งนี้!)
ses

@ses - เพิ่มคำอธิบาย (ยาวเล็กน้อย) เกี่ยวกับเรื่องนี้ ... นอกจากนี้ให้ดูที่stackoverflow.com/questions/13454347/monads-with-java-8/… - ฉันรู้ว่ามันเกือบจะเป็นเนื้อหาเดียวกัน
Faiz

หมายเหตุสำหรับผู้ที่ประสบข้อผิดพลาดในการคอมไพล์ลึกลับให้ใช้ foo? สำหรับคอลัมน์ตัวเลือก [T] มิฉะนั้นคุณจะพบว่าประเภทที่อ่านยากไม่ตรงกัน ขอบคุณ Faiz!
sventechie

1
นี่เป็นคำตอบที่ดี ... มันจะดีมากแม้ว่าจะสามารถอัปเดตสำหรับ Slick 3.0 ได้
Ixx

6

เนื่องจากไม่มีใครตอบคำถามนี้อาจช่วยให้คุณเริ่มต้นได้ ฉันไม่รู้จักเนียนเป็นอย่างดี

จากเอกสาร Slick :

การฝังแบบยก:

ทุกตารางต้องใช้เมธอด * ที่เกี่ยวข้องกับการฉายภาพเริ่มต้น สิ่งนี้อธิบายถึงสิ่งที่คุณได้รับกลับมาเมื่อคุณส่งคืนแถว (ในรูปแบบของวัตถุตาราง) จากแบบสอบถาม การฉายภาพ * ของ Slick ไม่จำเป็นต้องตรงกับที่แสดงในฐานข้อมูล คุณสามารถเพิ่มคอลัมน์ใหม่ (เช่นด้วยค่าที่คำนวณ) หรือละคอลัมน์บางคอลัมน์ก็ได้ตามต้องการ ประเภทที่ไม่ยกขึ้นที่สอดคล้องกับการฉายภาพ * ถูกกำหนดให้เป็นพารามิเตอร์ประเภทในตาราง สำหรับตารางธรรมดาที่ไม่ได้แมปจะเป็นประเภทคอลัมน์เดียวหรือทูเปิลของประเภทคอลัมน์

กล่าวอีกนัยหนึ่งคือเนียนจำเป็นต้องรู้วิธีจัดการกับแถวที่ส่งคืนจากฐานข้อมูล วิธีการที่คุณกำหนดจะใช้ฟังก์ชันตัวแยกวิเคราะห์เพื่อรวมนิยามคอลัมน์ของคุณให้เป็นสิ่งที่สามารถใช้ในแถวได้


เอิ๊ก. และ Projection เป็นเพียงการแสดงคอลัมน์ .. like: final class Projection2 [T1, T2] (override val _1: Column [T1], override val _2: Column [T2]) ขยาย Tuple2 (_1, _2) ด้วย Projection [( T1, T2)] {..
ses

ทีนี้ .. ได้อย่างไร: Bar มีวิธี 'ไม่ใช้'?
ses

2
Aha .. - คลาสเคสทั้งหมดใช้ลักษณะผลิตภัณฑ์และไม่ได้ใช้เป็นวิธีการของผลิตภัณฑ์ มายากล.
ses
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.