1. ホーム
  2. scala

[解決済み] scalaのslickメソッド、今ひとつ理解できない。

2023-03-25 14:49:10

質問

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 ?

どのように解決するのですか?

[アップデートのお知らせ] - に関する説明を (さらに) 追加しました。 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) のタプルを投影します。

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

    この場合、必要なプロジェクションを yield 節で定義しています。 の for 理解することができます。

  2. 今、約 <>Bar.unapply .

    これは、以下のようなものを提供します。 地図投影 .

    これまで、slickを使うとScalaでどのようにクエリーを表現できるかを見てきました。 を返すクエリをScalaで表現できることを見てきました。 カラムの投影 (または計算値)を返すクエリを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 . 2つの引数 Bar, Bar.unapply _ はslickにこの (Int, String) の投影がどのようにケースクラスにマッピングされなければならないかを教えてください。

    これは双方向のマッピングです。 Bar はケースクラスのコンストラクタで、これは に必要な情報です。 (id: Int, name: String) から Bar . そして unapply はその逆です。

    どこが unapply はどこから来るのでしょうか? これは Scala の標準的なメソッドで、普通の case class で利用可能です。 を定義するだけで、普通の case class で利用できます。 Bar を定義するだけで Bar.unapply であり は 抽出器 を返すために使用することができます。 idname は、その 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 _))
    
    

    ここでは q1Query を持つ をマップしたものです。 に投影されます。 Baz . 呼び出されたとき、それは ListBaz オブジェクトの

     val result: List[Baz] = q1.list
    
    
  3. 最後に、余談ですが .? が提供する オプション解除 - という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]
    
    
  4. スリックの使い方についてのコメントに対して for の内包を使用する方法についてのコメントに対するものです。

    どういうわけか、モナドはいつも現れ、説明の一部を要求してきます。 説明の一部であることを要求します...

    内包はコレクションだけに特化したものではありません。 どのような種類の モナド で使用することができ、コレクションは はScalaで利用できる多くの種類のモナド型のうちの1つだけです。

    しかし、コレクションは身近なものなので、説明のための良い出発点になります。

    しかし、コレクションは身近なものなので、説明の良い出発点になります。

    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では、for comprehensionは以下のような構文上の糖分です。 メソッド(ネストされた可能性のある)呼び出しのための構文解析です。上記のコード とは(多かれ少なかれ)等価です。

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

    基本的に filter , map , flatMap メソッド(言い換えれば モナド ) は for の代わりに ns . 良い例として は オプションモナド . これは前の例です ここで、同じ 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 , flatMapfilter というメソッドがあります。そのため 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 , mapfilter は を生成します。 Query の繰り返し変換により Query(Bars) の各呼び出しで filtermap . コレクションの場合 コレクションでは、これらのメソッドは実際にコレクションを反復処理し、フィルタリングします。 しかし、SlickではSQLを生成するために使用されます。詳しくはこちら。 Scala SlickはどのようにScalaのコードをJDBCに変換するのですか?