metodo scala slick non riesco a capire finora


89

Cerco di capire alcune opere di Slick e cosa richiede.

Eccolo un esempio:

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

Qualcuno potrebbe spiegarmi qual è lo scopo del *metodo qui, cos'è <>, perché unapply? e cos'è la proiezione - il metodo ~'restituisce l'istanza di Projection2?

Risposte:


198

[AGGIORNAMENTO] - aggiunta (ancora un'altra) spiegazione sulle forcomprensioni

  1. Il *metodo:

    Questo restituisce la proiezione predefinita , che è come descrivi:

    "tutte le colonne (o valori calcolati) che di solito mi interessano".

    La tua tabella potrebbe avere diversi campi; hai solo bisogno di un sottoinsieme per la tua proiezione predefinita. La proiezione predefinita deve corrispondere ai parametri di tipo della tabella.

    Prendiamolo uno alla volta. Senza il <>materiale, solo il *:

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

    Solo una definizione di tabella come questa ti consentirà di eseguire query come:

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

    la proiezione predefinita di (Int, String)porta a List[(Int, String)] per query semplici come queste.

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

    Qual è il tipo di q? È una Querycon la proiezione (String, Int). Quando viene richiamato, restituisce una Listdi (String, Int)tuple secondo la proiezione.

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

    In questo caso, hai definito la proiezione che desideri nella yieldclausola della forcomprensione.

  2. Ora circa <>e Bar.unapply.

    Ciò fornisce le cosiddette proiezioni mappate .

    Finora abbiamo visto come slick ti permetta di esprimere query in Scala che restituiscono una proiezione di colonne (o valori calcolati); Quindi, quando si eseguono queste query, è necessario pensare alla riga del risultato di una query come a una tupla Scala . Il tipo di tupla corrisponderà alla proiezione definita (dalla tua forcomprensione come nell'esempio precedente, o dalla *proiezione predefinita ). Questo è il motivo per cui field1 ~ field2restituisce una proiezione di Projection2[A, B]dove Aè il tipo di field1ed Bè il tipo di 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 
    }
    

    Abbiamo a che fare con tuple, che potrebbero essere ingombranti se abbiamo troppe colonne. Vorremmo pensare ai risultati non come a TupleNun oggetto con campi denominati.

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

    Come funziona? <>prende una proiezione Projection2[Int, String]e restituisce una proiezione mappata sul tipo Bar. I due argomenti Bar, Bar.unapply _ dicono a slick come questa (Int, String)proiezione deve essere mappata a una classe case.

    Questa è una mappatura a due vie; Barè il costruttore della classe case, quindi queste sono le informazioni necessarie per passare da (id: Int, name: String)a Bar. E unapply se l'hai indovinato, è per il contrario.

    Da dove unapplyviene? Questo è un metodo Scala standard disponibile per qualsiasi classe case ordinaria - la semplice definizione Barti dà Bar.unapplyun estrattore che può essere usato per recuperare ide nameche è Barstato costruito con:

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

    Quindi la tua proiezione predefinita può essere mappata alla classe case che più ti aspetti di usare:

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

    Oppure puoi anche averlo per query:

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

    Qui il tipo di q1 è a Querycon una proiezione mappata su Baz. Quando viene richiamato, restituisce un Listof Bazobjects:

     val result: List[Baz] = q1.list
  3. Infine, per inciso , le .?offerte Option Lifting - il modo Scala di trattare valori che potrebbero non esserlo.

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

    Che, concludendo, funzionerà bene con la tua definizione originale di 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. In risposta al commento su come Slick usa la forcomprensione:

    In qualche modo, le monadi riescono sempre a presentarsi e richiedono di far parte della spiegazione ...

    Perché le comprensioni non sono specifiche solo per le raccolte. Possono essere utilizzati su qualsiasi tipo di monade e le raccolte sono solo uno dei tanti tipi di monade disponibili in Scala.

    Ma poiché le raccolte sono familiari, rappresentano un buon punto di partenza per una spiegazione:

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

    In Scala, una comprensione for è zucchero sintattico per chiamate di metodi (possibilmente annidate): Il codice sopra è (più o meno) equivalente a:

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

    In sostanza, qualsiasi cosa con filter, map, flatMap metodi (in altre parole, una Monade ) può essere utilizzato in una forcomprensione al posto di ns. Un buon esempio è la monade Option . Ecco l'esempio precedente in cui la stessa foristruzione funziona sia sulle monadi Listche sulle Optionmonadi:

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

    Nell'ultimo esempio, la trasformazione sarebbe forse simile a questa:

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

    In Slick, le query sono monadica - sono solo oggetti con il map, flatMape filtermetodi. Quindi la forcomprensione (mostrata nella spiegazione del *metodo) si traduce semplicemente in:

    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
    

    Come si può vedere, flatMap, mape filtervengono utilizzati per generare un Querydalla trasformazione ripetuta di Query(Bars) uno con l'invocazione filtere map. Nel caso delle raccolte, questi metodi effettivamente iterano e filtrano la raccolta, ma in Slick vengono utilizzati per generare SQL. Maggiori dettagli qui: In che modo Scala Slick traduce il codice Scala in JDBC?


Nel blocco di spiegazione "1": non è ovvio che "val q =" sia WrappingQuery, sembra un List <Projection2> durante la lettura del codice. Come è possibile che si trasformi in Query ..? (Sto ancora giocando con le tue spiegazioni per capire come funziona. Grazie per questo!)
ses

@ses - aggiunta una spiegazione (leggermente lunga) su questo ... Inoltre, guarda stackoverflow.com/questions/13454347/monads-with-java-8/… - Mi sono reso conto che è quasi lo stesso contenuto.
Faiz

Nota per coloro che riscontrano misteriosi errori di compilazione, utilizzare foo.? per le colonne Opzione [T] o otterrai una mancata corrispondenza di tipo difficile da leggere. Grazie, Faiz!
sventechie

1
Questa è un'ottima risposta ... sarebbe fantastico se potesse essere aggiornato per Slick 3.0
Ixx

6

Poiché nessun altro ha risposto, questo potrebbe aiutarti a iniziare. Non conosco molto bene Slick.

Dalla documentazione di Slick :

Incorporamento rialzato:

Ogni tabella richiede un * metodo che contenga una proiezione predefinita. Descrive cosa si ottiene quando si restituiscono righe (sotto forma di un oggetto tabella) da una query. La proiezione di Slick * non deve necessariamente corrispondere a quella nel database. Puoi aggiungere nuove colonne (ad esempio con valori calcolati) o omettere alcune colonne come preferisci. Il tipo non rialzato corrispondente alla proiezione * viene fornito come parametro di tipo in Tabella. Per tabelle semplici non mappate, questo sarà un tipo di colonna singola o una tupla di tipi di colonna.

In altre parole, slick deve sapere come gestire una riga restituita dal database. Il metodo che hai definito utilizza le loro funzioni di combinatore parser per combinare le definizioni di colonna in qualcosa che può essere utilizzato su una riga.


ook. e Projection è solo una rappresentazione di colonne .. come: final class Projection2 [T1, T2] (override val _1: Column [T1], override val _2: Column [T2]) extends Tuple2 (_1, _2) with Projection [( T1, T2)] {..
ses

Ora ... come mai: Bar ha il metodo "inapplicabile"?
ses

2
Aha .. - tutte le classi case implementano il tratto Product e unpply è il metodo di Product. Magia.
ses
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.