phương pháp scala slick tôi không thể hiểu được cho đến nay


89

Tôi cố gắng hiểu một số hoạt động của Slick và những gì nó yêu cầu.

Đây là một ví dụ:

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

Ai đó có thể giải thích cho tôi mục đích của *phương pháp ở đây là gì <>, tại sao unapplykhông? và Projection là gì - method ~'trả về instance của Projection2?

Câu trả lời:


198

[UPDATE] - đã thêm (nhưng một giải thích khác) về phần forhiểu

  1. Các * phương pháp:

    Điều này trả về phép chiếu mặc định - đó là cách bạn mô tả:

    'tất cả các cột (hoặc các giá trị được tính toán) mà tôi thường quan tâm'.

    Bảng của bạn có thể có một số trường; bạn chỉ cần một tập hợp con cho phép chiếu mặc định của mình. Phép chiếu mặc định phải khớp với các tham số kiểu của bảng.

    Hãy làm từng cái một. Không có những <>thứ, chỉ có *:

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

    Chỉ một định nghĩa bảng như vậy sẽ cho phép bạn thực hiện các truy vấn như:

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

    phép chiếu mặc định của các (Int, String)khách hàng tiềm năng đến một List[(Int, String)] cho các truy vấn đơn giản như sau.

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

    Loại của là qgì? Nó là một Queryvới hình chiếu (String, Int). Khi gọi, nó sẽ trả về một Listtrong (String, Int)các bộ theo dự báo.

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

    Trong trường hợp này, bạn đã xác định phép chiếu mà bạn muốn trong yieldmệnh đề của phần forhiểu.

  2. Bây giờ về <>Bar.unapply.

    Điều này cung cấp cái được gọi là Phép chiếu được Ánh xạ .

    Cho đến nay, chúng ta đã thấy cách slick cho phép bạn thể hiện các truy vấn trong Scala trả về hình chiếu các cột (hoặc các giá trị được tính toán); Vì vậy, khi thực hiện các truy vấn này, bạn phải nghĩ về hàng kết quả của một truy vấn là một bộ Scala . Loại bộ tuple sẽ khớp với Phép chiếu được xác định (theo cách forhiểu của bạn như trong ví dụ trước, theo *phép chiếu mặc định ). Đây là lý do tại sao field1 ~ field2trả về một phép chiếu về Projection2[A, B]đâu Alà loại field1Blà loại 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 
    }

    Chúng tôi đang xử lý các bộ giá trị, có thể cồng kềnh nếu chúng tôi có quá nhiều cột. Chúng tôi muốn nghĩ đến kết quả không phải là TupleNmột số đối tượng có các trường được đặt tên.

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

    Cái này hoạt động ra sao? <>lấy một phép chiếu Projection2[Int, String]và trả về một phép chiếu được ánh xạ trên kiểu Bar. Hai đối số Bar, Bar.unapply _ cho biết rõ ràng cách này(Int, String) phép chiếu phải được ánh xạ tới một lớp trường hợp.

    Đây là một ánh xạ hai chiều; Barlà hàm tạo lớp case, vì vậy đó là thông tin cần thiết để chuyển từ (id: Int, name: String)a Bar. Và unapply nếu bạn đã đoán ra, thì ngược lại.

    Từ đâu unapplyđến? Đây là một phương pháp Scala tiêu chuẩn có sẵn cho bất kỳ lớp trường hợp thông thường nào - chỉ cần xác định Barsẽ cung cấp cho bạn một Bar.unapplyphương thức giải nén có thể được sử dụng để lấy lại idnamecái Barđược xây dựng bằng:

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

    Vì vậy, phép chiếu mặc định của bạn có thể được ánh xạ tới lớp trường hợp mà bạn mong muốn sử dụng nhất:

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

    Hoặc bạn thậm chí có thể có nó cho mỗi truy vấn:

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

    Ở đây loại q1 là a Queryvới một phép chiếu được ánh xạ tới Baz. Khi được gọi, nó trả về một Listtrong Bazcác đối tượng:

     val result: List[Baz] = q1.list
  3. Cuối cùng, bên cạnh đó, .?cung cấp tùy chọn Nâng - cách Scala xử lý các giá trị có thể không.

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

    Điều này, kết thúc, sẽ hoạt động tốt với định nghĩa ban đầu của bạn về 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. Trả lời nhận xét về cách Slick sử dụng các từ forhiểu:

    Bằng cách nào đó, các monads luôn cố gắng xuất hiện và yêu cầu trở thành một phần của lời giải thích ...

    Đối với sự hiểu biết không chỉ dành riêng cho các bộ sưu tập. Chúng có thể được sử dụng trên bất kỳ loại Đơn nguyên nào , và các bộ sưu tập chỉ là một trong nhiều loại đơn nguyên có sẵn trong Scala.

    Nhưng vì các bộ sưu tập đã quen thuộc, chúng là một điểm khởi đầu tốt để giải thích:

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

    Trong Scala, một để hiểu là đường cú pháp cho các lệnh gọi phương thức (có thể lồng nhau): Đoạn mã trên (nhiều hơn hoặc ít hơn) tương đương với:

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

    Về cơ bản, bất cứ điều gì với filter, map, flatMap phương pháp (hay nói cách khác, một đơn nguyên ) có thể được sử dụng trong một forsự hiểu biết thay ns. Một ví dụ điển hình là đơn nguyên Option . Đây là ví dụ trước đó trong đó forcâu lệnh tương tự hoạt động trên cả monads Listcũng như 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

    Trong ví dụ cuối cùng, phép biến đổi có thể trông như thế này:

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

    Trong Slick, truy vấn là monadic - họ chỉ là đối tượng với map, flatMapfilterphương pháp. Vì vậy, sự forhiểu biết (được hiển thị trong phần giải thích của *phương pháp) chỉ chuyển thành:

    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

    Như bạn có thể thấy, flatMap, mapfilterđược sử dụng để tạo ra một Querybởi sự biến đổi lặp lại Query(Bars) với nhau gọi trình filtermap. Trong trường hợp tập hợp, các phương thức này thực sự lặp lại và lọc tập hợp nhưng trong Slick, chúng được sử dụng để tạo SQL. Thông tin chi tiết tại đây: Scala Slick dịch mã Scala sang JDBC như thế nào?


Trong khối giải thích '1': Không rõ ràng rằng 'val q =' là WrappingQuery, nó trông giống như một Danh sách <Projection2> khi đọc mã. Làm thế nào nó có thể chuyển đổi thành Truy vấn ..? (Tôi vẫn đang giải thích của bạn để hiểu cách hoạt động của nó. Cảm ơn bạn vì điều này!)
ses

@ses - đã thêm một lời giải thích (hơi dài) về điều này ... Ngoài ra, hãy xem stackoverflow.com/questions/13454347/monads-with-java-8/… - Tôi nhận ra rằng nội dung gần như giống nhau.
Faiz

Lưu ý đối với những người gặp lỗi biên dịch bí ẩn, hãy sử dụng foo.? cho các cột Tùy chọn [T] hoặc bạn sẽ gặp khó khăn khi đọc loại không khớp. Cảm ơn, Faiz!
sventechie

1
Đây là một câu trả lời tuyệt vời ... sẽ thật tuyệt vời nếu nó có thể được cập nhật cho Slick 3.0
Ixx

6

Vì chưa có ai khác trả lời nên điều này có thể giúp bạn bắt đầu. Tôi không biết Slick cho lắm.

Từ tài liệu Slick :

Nhúng nâng lên:

Mọi bảng đều yêu cầu phương thức * chứa một phép chiếu mặc định. Điều này mô tả những gì bạn nhận được khi trả về các hàng (ở dạng đối tượng bảng) từ một truy vấn. Phép chiếu * của Slick không nhất thiết phải khớp với phép chiếu trong cơ sở dữ liệu. Bạn có thể thêm các cột mới (ví dụ: với các giá trị được tính toán) hoặc bỏ qua một số cột tùy thích. Kiểu không nâng lên tương ứng với phép chiếu * được đưa ra làm tham số kiểu cho Bảng. Đối với các bảng đơn giản, không được ánh xạ, đây sẽ là một loại cột đơn hoặc nhiều loại cột.

Nói cách khác, slick cần biết cách đối phó với một hàng được trả về từ cơ sở dữ liệu. Phương thức bạn đã xác định sử dụng các hàm tổ hợp phân tích cú pháp của chúng để kết hợp các định nghĩa cột của bạn thành một thứ gì đó có thể được sử dụng trên một hàng.


ook. và Phép chiếu chỉ là biểu diễn của các cột .. như: lớp cuối cùng Projection2 [T1, T2] (ghi đè val _1: Cột [T1], ghi đè val _2: Cột [T2]) mở rộng Tuple2 (_1, _2) bằng Phép chiếu [( T1, T2)] {..
ses

Bây giờ .. sao lại thế: Bar có phương thức 'không áp dụng'?
ses

2
Aha .. - tất cả các lớp trường hợp đều triển khai đặc điểm Sản phẩm và không áp dụng là phương thức của Sản phẩm. Ma thuật.
ses
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.