slick對超過22個屬性的表進行對映的兩種辦法

zzzzMing發表於2018-02-05

版權宣告:本文為博主原創文章,未經博主允許不得轉載

slick是scala的一個FRM(Functional Relational Mapper)框架,即函式式的關聯式資料庫程式設計工具庫。使用slick不同於使用java的hibernate或者是mybatis,對其進行迭代開發非常方便,因為其對錶的對映基於函式式的程式設計方式。

使用slick對資料庫表對映比較方便。

比如有一個表

CREATE TABLE Persons
(
id int,
LastName varchar(255),
FirstName varchar(255),
Address varchar(255),
City varchar(255)
)

那麼slick可以有兩種方式進行對映,一種是使用scala的資料結構Tuple,一種是使用csse class,先看第一種

//使用Tuple
class TagsTree(tag: Tag) extends Table[(Int, String, String, String,String)](tag, "Persons") {
    def id= column[Int]("id") 
    def LastName= column[String]("LastName")
    def FirstName= column[String]("FirstName")
    def Address= column[String]("Address")
    def City= column[String]("City")
    def * = (id,LastName,FirstName,Address,City);
  }

再看第二種case class的方式:

case class Person(id:Int,lastName:String,firstName:String,Address:String,city:String);
class TagsTree(tag: Tag) extends Table[Person](tag, "Persons") {
    def id= column[Int]("id") 
    def LastName= column[String]("LastName")
    def FirstName= column[String]("FirstName")
    def Address= column[String]("Address")
    def City= column[String]("City")
    def * = (id,LastName,FirstName,Address,City) <> (Person.tupled(), Person.unapply())
); }

這裡要說明一下,<>操作符可以看作某種對映,tupled()會將接受case class本身同樣的引數然後生成一個Tuple。unapply是scala的一個語法糖,會返回一個Option[Int,String....]。這便是使用case class的方法了。

但當遇到表中的欄位超過22個的時候就有問題了,因為scala規定構造引數不能超過22個,那這個時候怎麼辦呢,有兩個辦法,一個是使用slick提供的HList。

使用HList

import slick.collection.heterogeneous.{ HList, HCons, HNil } 
 
 
type hList =
    String :: String :: Option[String] :: Option[String] :: Option[String] :: Option[String] ::
      String :: String :: String :: String :: String ::   Int :: Int :: String :: String :: String ::
      Int :: Int :: Int ::  Int :: Int :: Int ::   Int :: Int :: Int ::
      HNil;  
//需要以HNil結尾
  class Table(tag: Tag) extends Table[hList](tag, "myTableName") {
    def name1 = column[String]("name1")
    def name2 = column[String]("name2")
    def name3 = column[Option[String]]("name3")
    def name4 = column[Option[String]]("name4")
    def name5 = column[Option[String]]("name5")
    def name6 = column[Option[String]]("name6")
    def name7 = column[String]("name7")
    def name8 = column[String]("name8")
    def name9 = column[String]("name9")
    def name10 = column[String]("name10")
    def name11 = column[String]("name11")
    def name12 = column[Int]("name12")
    def name13 = column[Int]("name13")
    def name14 = column[String]("name14")
    def name15 = column[String]("name15")
    def name16 = column[String]("name16")
    def name17 = column[Int]("name17")
    def name18= column[Int]("name18")
    def name19 = column[Int]("name19")
    def name20 = column[Int]("name20")
    def name21 = column[Int]("name21")
    def name22 = column[Int]("name22")
    def name23 = column[Int]("name23")
    def name24 = column[Int]("name24")
    def name25 = column[Int]("name25")
    def * =   name1 :: name2 :: name3 :: name4 :: name5 :: name6 ::
      name7 :: name8 ::   name8 :: name9 :: name10 ::
      name11 :: name12 :: name13 :: name14 :: name15 ::
      name16 ::  name17 :: name18 ::   name19 :: name20 :: name21 ::
      name22 :: name23 ::    name24 ::  name25 :: 
      HNil
//需要以HNil結尾
  }

 不過這種有個缺點,那就是當表中某些屬性可能為空,這時需要使用Option[String]()這種資料型別來存放屬性。但HList查詢出來的時候只會像一個字串一樣的Some("value")。

另一個方法是思路比較簡單,既然一個case class放不下,那就拆唄

//程式碼出處 https://github.com/slick/slick/blob/2.1.0-RC1/slick-testkit/src/main/scala/com/typesafe/slick/testkit/tests/JdbcMapperTest.scala#L106

def testWideMappedEntity {
    case class Part(i1: Int, i2: Int, i3: Int, i4: Int, i5: Int, i6: Int)
    case class Whole(id: Int, p1: Part, p2: Part, p3: Part, p4: Part)

    class T(tag: Tag) extends Table[Whole](tag, "t_wide") {
      def id = column[Int]("id", O.PrimaryKey)
      def p1i1 = column[Int]("p1i1")
      def p1i2 = column[Int]("p1i2")
      def p1i3 = column[Int]("p1i3")
      def p1i4 = column[Int]("p1i4")
      def p1i5 = column[Int]("p1i5")
      def p1i6 = column[Int]("p1i6")
      def p2i1 = column[Int]("p2i1")
      def p2i2 = column[Int]("p2i2")
      def p2i3 = column[Int]("p2i3")
      def p2i4 = column[Int]("p2i4")
      def p2i5 = column[Int]("p2i5")
      def p2i6 = column[Int]("p2i6")
      def p3i1 = column[Int]("p3i1")
      def p3i2 = column[Int]("p3i2")
      def p3i3 = column[Int]("p3i3")
      def p3i4 = column[Int]("p3i4")
      def p3i5 = column[Int]("p3i5")
      def p3i6 = column[Int]("p3i6")
      def p4i1 = column[Int]("p4i1")
      def p4i2 = column[Int]("p4i2")
      def p4i3 = column[Int]("p4i3")
      def p4i4 = column[Int]("p4i4")
      def p4i5 = column[Int]("p4i5")
      def p4i6 = column[Int]("p4i6")
      def * = (
        id,
        (p1i1, p1i2, p1i3, p1i4, p1i5, p1i6),
        (p2i1, p2i2, p2i3, p2i4, p2i5, p2i6),
        (p3i1, p3i2, p3i3, p3i4, p3i5, p3i6),
        (p4i1, p4i2, p4i3, p4i4, p4i5, p4i6)
      ).shaped <> ({ case (id, p1, p2, p3, p4) =>
        // We could do this without .shaped but then we'd have to write a type annotation for the parameters
        Whole(id, Part.tupled.apply(p1), Part.tupled.apply(p2), Part.tupled.apply(p3), Part.tupled.apply(p4))
      }, { w: Whole =>
        def f(p: Part) = Part.unapply(p).get
        Some((w.id, f(w.p1), f(w.p2), f(w.p3), f(w.p4)))
      })
    }
    val ts = TableQuery[T]

    val oData = Whole(0,
      Part(11, 12, 13, 14, 15, 16),
      Part(21, 22, 23, 24, 25, 26),
      Part(31, 32, 33, 34, 35, 36),
      Part(41, 42, 43, 44, 45, 46)
    )

    ts.ddl.create
    ts.insert(oData)

    assertEquals(oData, ts.first)
  }

 這裡再那個.shaped是可以省略的,<>操作符後面看似很複雜,其實同樣它後面有兩個引數,一個Tuple和一個unapply方法,僅此而已。同時這裡將對映寫在一個方法裡,這也是為什麼slick稱之為FRM。

 

相關文章