在Slick官方文档中描述:连接后台数据库后,需要通过定义Projection,即def * 来进行具体库表列column的选择和排序。通过Projection我们可以选择库表中部分列、也可以增加一些自定义列computed column。具体来说Projection提供了数据库表列与Scala值的对应。例如def * = (column1,column2)把库表的column1和column2与(Int,String)对应,column1[Int],column2[String]。也可以说是与定义column的类参数进行对应。从Slick源代码中我们可以找到Projection定义:

abstract class AbstractTable[T](val tableTag: Tag, val schemaName: Option[String], val tableName: String) extends Rep[T] {
/** The client-side type of the table as defined by its * projection */
type TableElementType
...
/** The * projection of the table used as default for queries and inserts.
* Should include all columns as a tuple, HList or custom shape and optionally
* map them to a custom entity type using the <> operator.
* The `ProvenShape` return type ensures that
* there is a `Shape` available for translating between the `Column`-based
* type in * and the client-side type without `Column` in the table's type
* parameter. */
def * : ProvenShape[T]
...
}

我们看到Projection是个ProvenShape[T]类。再看看ProvenShape是怎么定义的:

/** A limited version of ShapedValue which can be constructed for every type
* that has a valid shape. We use it to enforce that a table's * projection
* has a valid shape. A ProvenShape has itself a Shape so it can be used in
* place of the value that it wraps for purposes of packing and unpacking. */
trait ProvenShape[U] {
def value: Any
val shape: Shape[_ <: FlatShapeLevel, _, U, _]
def packedValue[R](implicit ev: Shape[_ <: FlatShapeLevel, _, U, R]): ShapedValue[R, U]
def toNode = packedValue(shape).toNode
} object ProvenShape {
/** Convert an appropriately shaped value to a ProvenShape */
implicit def proveShapeOf[T, U](v: T)(implicit sh: Shape[_ <: FlatShapeLevel, T, U, _]): ProvenShape[U] =
new ProvenShape[U] {
def value = v
val shape: Shape[_ <: FlatShapeLevel, _, U, _] = sh.asInstanceOf[Shape[FlatShapeLevel, _, U, _]]
def packedValue[R](implicit ev: Shape[_ <: FlatShapeLevel, _, U, R]): ShapedValue[R, U] = ShapedValue(sh.pack(value).asInstanceOf[R], sh.packedShape.asInstanceOf[Shape[FlatShapeLevel, R, U, _]])
} /** The Shape for a ProvenShape */
implicit def provenShapeShape[T, P](implicit shape: Shape[_ <: FlatShapeLevel, T, T, P]): Shape[FlatShapeLevel, ProvenShape[T], T, P] = new Shape[FlatShapeLevel, ProvenShape[T], T, P] {
def pack(value: Mixed): Packed =
value.shape.pack(value.value.asInstanceOf[value.shape.Mixed]).asInstanceOf[Packed]
def packedShape: Shape[FlatShapeLevel, Packed, Unpacked, Packed] =
shape.packedShape.asInstanceOf[Shape[FlatShapeLevel, Packed, Unpacked, Packed]]
def buildParams(extract: Any => Unpacked): Packed =
shape.buildParams(extract.asInstanceOf[Any => shape.Unpacked])
def encodeRef(value: Mixed, path: Node) =
value.shape.encodeRef(value.value.asInstanceOf[value.shape.Mixed], path)
def toNode(value: Mixed): Node =
value.shape.toNode(value.value.asInstanceOf[value.shape.Mixed])
}
}

从implicit def proveShapeOf[T,U](v:T):ProvenShape[U]可以得出对于任何T,如果能提供Shape[_,_,T,U,_]的隐式实例implicit instance的话就能构建出ProvenShape[U]。我们再看看什么是Shape:

/** A type class that encodes the unpacking `Mixed => Unpacked` of a
* `Query[Mixed]` to its result element type `Unpacked` and the packing to a
* fully packed type `Packed`, i.e. a type where everything which is not a
* transparent container is wrapped in a `Column[_]`.
*
* =Example:=
* - Mixed: (Column[Int], Column[(Int, String)], (Int, Option[Double]))
* - Unpacked: (Int, (Int, String), (Int, Option[Double]))
* - Packed: (Column[Int], Column[(Int, String)], (Column[Int], Column[Option[Double]]))
* - Linearized: (Int, Int, String, Int, Option[Double])
*/
abstract class Shape[Level <: ShapeLevel, -Mixed, Unpacked_, Packed_] {...}

上面的Mixed就是ProvenShape的T,Unpacked就是U。如此看来T代表Query[T]的T,而U就是返回结果类型了。如果我们能提供T的Shape隐式实例就能把U升格成ProvenShape[U]。我们来看看Slick官方文件上的例子:

  import scala.reflect.ClassTag
// A custom record class
case class Pair[A, B](a: A, b: B) // A Shape implementation for Pair
final class PairShape[Level <: ShapeLevel, M <: Pair[_,_], U <: Pair[_,_] : ClassTag, P <: Pair[_,_]](
val shapes: Seq[Shape[_, _, _, _]])
extends MappedScalaProductShape[Level, Pair[_,_], M, U, P] {
def buildValue(elems: IndexedSeq[Any]) = Pair(elems(), elems())
def copy(shapes: Seq[Shape[_ <: ShapeLevel, _, _, _]]) = new PairShape(shapes)
} implicit def pairShape[Level <: ShapeLevel, M1, M2, U1, U2, P1, P2](
implicit s1: Shape[_ <: Level, M1, U1, P1], s2: Shape[_ <: Level, M2, U2, P2]
) = new PairShape[Level, Pair[M1, M2], Pair[U1, U2], Pair[P1, P2]](Seq(s1, s2)) // Use it in a table definition
class A(tag: Tag) extends Table[Pair[Int, String]](tag, "shape_a") {
def id = column[Int]("id", O.PrimaryKey)
def s = column[String]("s")
def * = Pair(id, s)
}
val as = TableQuery[A]

现在Projection可以写成Pair(id,s)。也就是说因为有了implicit def pairShape[...](...):PairShape所以Pair(id,s)被升格成ProvenShape[Pair]。这样Query的返回类型就是Seq[Pair]了。实际上Slick本身提供了Tuple、Case Class、HList等类型的默认Shape隐式实例,所以我们可以把Projection直接写成 def * = (...) 或 Person(...) 或 Int::String::HNil。下面是Tuple的默认Shape:

trait TupleShapeImplicits {
@inline
implicit final def tuple1Shape[Level <: ShapeLevel, M1, U1, P1](implicit u1: Shape[_ <: Level, M1, U1, P1]): Shape[Level, Tuple1[M1], Tuple1[U1], Tuple1[P1]] =
new TupleShape[Level, Tuple1[M1], Tuple1[U1], Tuple1[P1]](u1)
@inline
implicit final def tuple2Shape[Level <: ShapeLevel, M1,M2, U1,U2, P1,P2](implicit u1: Shape[_ <: Level, M1, U1, P1], u2: Shape[_ <: Level, M2, U2, P2]): Shape[Level, (M1,M2), (U1,U2), (P1,P2)] =
new TupleShape[Level, (M1,M2), (U1,U2), (P1,P2)](u1,u2)
...

回到主题,下面是一个典型的Slick数据库表读取例子:

   class TupleTypedPerson(tag: Tag) extends Table[(
Option[Int],String,Int,Option[String])](tag,"PERSON") {
def id = column[Int]("id",O.PrimaryKey,O.AutoInc)
def name = column[String]("name")
def age = column[Int]("age")
def alias = column[Option[String]]("alias")
def * = (id.?,name,age,alias)
}
val tupleTypedPerson = TableQuery[TupleTypedPerson] val db = Database.forURL("jdbc:h2:mem:test1;DB_CLOSE_DELAY=-1", driver = "org.h2.Driver")
val createSchemaAction = tupleTypedPerson.schema.create
Await.ready(db.run(createSchemaAction),Duration.Inf)
val initDataAction = DBIO.seq {
tupleTypedPerson ++= Seq(
(Some(),"Tiger Chan", , Some("Tiger_XC")),
(Some(),"Johnny Cox", , None),
(Some(),"Cathy Williams", , Some("Catty")),
(Some(),"David Wong", , None)
)
}
Await.ready(db.run(initDataAction),Duration.Inf)
val queryAction = tupleTypedPerson.result Await.result(db.run(queryAction),Duration.Inf).foreach {row =>
println(s"${row._1.get} ${row._2} ${row._4.getOrElse("")}, ${row._3}")
}

在这个例子的表结构定义里默认的Projection是个Tuple。造成的后果是返回的结果行不含字段名,只有字段位置。使用这样的行数据很容易错误对应,或者重复确认正确的列值会影响工作效率。如果返回的结果类型是Seq[Person]这样的话:Person是个带属性的对象如case class,那么我们就可以通过IDE提示的字段名称来选择字段了。上面提过返回结果类型可以通过ProvenShape来确定,如果能实现ProvenShape[A] => ProvenShape[B]这样的转换处理,那么我们就可以把返回结果行类型从Tuple变成有字段名的类型了:

   class Person(val id: Option[Int],
val name: String, val age: Int, val alias: Option[String])
def toPerson(t: (Option[Int],String,Int,Option[String])) = new Person (
t._1,t._2,t._3,t._4
)
def fromPerson(p: Person) = Some((p.id,p.name,p.age,p.alias))
class TupleMappedPerson(tag: Tag) extends Table[
Person](tag,"PERSON") {
def id = column[Int]("id",O.PrimaryKey,O.AutoInc)
def name = column[String]("name")
def age = column[Int]("age")
def alias = column[Option[String]]("alias")
def * = (id.?,name,age,alias) <> (toPerson,fromPerson)
}
val tupleMappedPerson = TableQuery[TupleMappedPerson] Await.result(db.run(tupleMappedPerson.result),Duration.Inf).foreach {row =>
println(s"${row.id.get} ${row.name} ${row.alias.getOrElse("")}, ${row.age}")
}

我们用<>函数进行了Tuple=>Person转换。注意toPerson和fromPerson这两个相互转换函数。如果Person是个case class,那么Person.tupled和Person.unapply就是它自备的转换函数,我们可以用case class来构建MappedProjection:

   case class Person(id: Option[Int]=None, name: String, age: Int, alias: Option[String])

   class MappedTypePerson(tag: Tag) extends Table[Person](tag,"PERSON") {
def id = column[Int]("id",O.PrimaryKey,O.AutoInc)
def name = column[String]("name")
def age = column[Int]("age")
def alias = column[Option[String]]("alias")
def * = (id.?,name,age,alias) <> (Person.tupled,Person.unapply)
}
val mappedPeople = TableQuery[MappedTypePerson]

从上面两个例子里我们似乎可以得出ProvenShape[T]的T类型就是Table[T]的T,也就是返回结果行的类型了。我们可以用同样方式来进行HList与Person转换:

   def hlistToPerson(hl: Option[Int]::String::Int::(Option[String])::HNil) =
new Person(hl(),hl(),hl(),hl())
def personToHList(p: Person) = Some(p.id::p.name::p.age::p.alias::HNil)
class HListPerson(tag: Tag) extends Table[Person](tag,"PERSON") {
def id = column[Int]("id",O.PrimaryKey,O.AutoInc)
def name = column[String]("name")
def age = column[Int]("age")
def alias = column[Option[String]]("alias")
def * = (id.?)::name::age::alias::HNil <> (hlistToPerson,personToHList)
}
val hlistPerson = TableQuery[HListPerson]
Await.result(db.run(hlistPerson.result),Duration.Inf).foreach {row =>
println(s"${row.id.get} ${row.name} ${row.alias.getOrElse("")}, ${row.age}")
}

同样,必须首先实现hlistToPerson和personToHList转换函数。现在Table的类型参数必须是Person。上面的Projection都是对Table默认Projection的示范。实际上我们可以针对每个Query来自定义Projection,如下:

  case class YR(name: String, yr: Int)

   val qYear = for {
p <- hlistPerson
} yield ((p.name, p.age) <> (YR.tupled,YR.unapply)) Await.result(db.run(qYear.result),Duration.Inf).foreach {row =>
println(s"${row.name} ${row.yr}")
}

上面这个例子里我们构建了基于case class YR的projection。在join table query情况下只能通过这种方式来构建Projection,看看下面这个例子:

   case class Title(id: Int, title: String)
class PersonTitle(tag: Tag) extends Table[Title](tag,"TITLE") {
def id = column[Int]("id")
def title = column[String]("title")
def * = (id,title) <> (Title.tupled,Title.unapply)
}
val personTitle = TableQuery[PersonTitle]
val createTitleAction = personTitle.schema.create
Await.ready(db.run(createTitleAction),Duration.Inf)
val initTitleData = DBIO.seq {
personTitle ++= Seq(
Title(,"Manager"),
Title(,"Programmer"),
Title(,"Clerk")
)
}
Await.ready(db.run(initTitleData),Duration.Inf) case class Titles(id: Int, name: String, title: String)
val qPersonWithTitle = for {
p <- hlistPerson
t <- personTitle if p.id === t.id
} yield ((p.id,p.name,t.title) <> (Titles.tupled,Titles.unapply))
Await.result(db.run(qPersonWithTitle.result),Duration.Inf).foreach {row =>
println(s"${row.id} ${row.name}, ${row.title}")
}

现在对任何形式的Query结果我们都能使用强类型(strong typed)的字段名称来进行操作了。

下面是本次示范的源代码:

 import slick.collection.heterogeneous.{ HList, HCons, HNil }
import slick.collection.heterogeneous.syntax._
import slick.driver.H2Driver.api._ import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration._
import scala.concurrent.{Await, Future} object chkProjection { class TupleTypedPerson(tag: Tag) extends Table[(
Option[Int],String,Int,Option[String])](tag,"PERSON") {
def id = column[Int]("id",O.PrimaryKey,O.AutoInc)
def name = column[String]("name")
def age = column[Int]("age")
def alias = column[Option[String]]("alias")
def * = (id.?,name,age,alias)
}
val tupleTypedPerson = TableQuery[TupleTypedPerson] val db = Database.forURL("jdbc:h2:mem:test1;DB_CLOSE_DELAY=-1", driver = "org.h2.Driver")
val createSchemaAction = tupleTypedPerson.schema.create
Await.ready(db.run(createSchemaAction),Duration.Inf)
val initDataAction = DBIO.seq {
tupleTypedPerson ++= Seq(
(Some(),"Tiger Chan", , Some("Tiger_XC")),
(Some(),"Johnny Cox", , None),
(Some(),"Cathy Williams", , Some("Catty")),
(Some(),"David Wong", , None)
)
}
Await.ready(db.run(initDataAction),Duration.Inf) val queryAction = tupleTypedPerson.result Await.result(db.run(queryAction),Duration.Inf).foreach {row =>
println(s"${row._1.get} ${row._2} ${row._4.getOrElse("")}, ${row._3}")
} class Person(val id: Option[Int],
val name: String, val age: Int, val alias: Option[String])
def toPerson(t: (Option[Int],String,Int,Option[String])) = new Person (
t._1,t._2,t._3,t._4
)
def fromPerson(p: Person) = Some((p.id,p.name,p.age,p.alias))
class TupleMappedPerson(tag: Tag) extends Table[
Person](tag,"PERSON") {
def id = column[Int]("id",O.PrimaryKey,O.AutoInc)
def name = column[String]("name")
def age = column[Int]("age")
def alias = column[Option[String]]("alias")
def * = (id.?,name,age,alias) <> (toPerson,fromPerson)
}
val tupleMappedPerson = TableQuery[TupleMappedPerson] Await.result(db.run(tupleMappedPerson.result),Duration.Inf).foreach {row =>
println(s"${row.id.get} ${row.name} ${row.alias.getOrElse("")}, ${row.age}")
} def hlistToPerson(hl: Option[Int]::String::Int::(Option[String])::HNil) =
new Person(hl(),hl(),hl(),hl())
def personToHList(p: Person) = Some(p.id::p.name::p.age::p.alias::HNil)
class HListPerson(tag: Tag) extends Table[Person](tag,"PERSON") {
def id = column[Int]("id",O.PrimaryKey,O.AutoInc)
def name = column[String]("name")
def age = column[Int]("age")
def alias = column[Option[String]]("alias")
def * = (id.?)::name::age::alias::HNil <> (hlistToPerson,personToHList)
}
val hlistPerson = TableQuery[HListPerson]
Await.result(db.run(hlistPerson.result),Duration.Inf).foreach {row =>
println(s"${row.id.get} ${row.name} ${row.alias.getOrElse("")}, ${row.age}")
} case class YR(name: String, yr: Int) val qYear = for {
p <- hlistPerson
} yield ((p.name, p.age) <> (YR.tupled,YR.unapply)) Await.result(db.run(qYear.result),Duration.Inf).foreach {row =>
println(s"${row.name} ${row.yr}")
} case class Title(id: Int, title: String)
class PersonTitle(tag: Tag) extends Table[Title](tag,"TITLE") {
def id = column[Int]("id")
def title = column[String]("title")
def * = (id,title) <> (Title.tupled,Title.unapply)
}
val personTitle = TableQuery[PersonTitle]
val createTitleAction = personTitle.schema.create
Await.ready(db.run(createTitleAction),Duration.Inf)
val initTitleData = DBIO.seq {
personTitle ++= Seq(
Title(,"Manager"),
Title(,"Programmer"),
Title(,"Clerk")
)
}
Await.ready(db.run(initTitleData),Duration.Inf) case class Titles(id: Int, name: String, title: String)
val qPersonWithTitle = for {
p <- hlistPerson
t <- personTitle if p.id === t.id
} yield ((p.id,p.name,t.title) <> (Titles.tupled,Titles.unapply))
Await.result(db.run(qPersonWithTitle.result),Duration.Inf).foreach {row =>
println(s"${row.id} ${row.name}, ${row.title}")
} }

细谈Slick(6)- Projection:ProvenShape,强类型的Query结果类型的更多相关文章

  1. 细谈Slick(5)- 学习体会和将来实际应用的一些想法

    通过一段时间的学习和了解以及前面几篇关于Slick的讨论后对Slick这个函数式数据库编程工具有了些具体的了解.回顾我学习Slick的目的,产生了许多想法,觉着应该从实际的工作应用角度把我对Slick ...

  2. 浅谈Slick(3)- Slick201:从fp角度了解Slick

    我在上期讨论里已经成功的创建了一个简单的Slick项目,然后又尝试使用了一些最基本的功能.Slick是一个FRM(Functional Relational Mapper),是为fp编程提供的scal ...

  3. Java程序员从笨鸟到菜鸟之(五十一)细谈Hibernate(二)开发第一个hibernate基本详解

    在上篇博客中,我们介绍了<hibernate基本概念和体系结构>,也对hibernate框架有了一个初步的了解,本文我将向大家简单介绍Hibernate的核心API调用库,并讲解一下它的基 ...

  4. Spark RDD概念学习系列之细谈RDD的弹性(十六)

    细谈RDD的弹性  所谓,弹性,是指在内存不够时可以与磁盘进行交换. 弹性之一:自动的进行内存和磁盘数据存储的切换   弹性之二:基于Lineage(血缘)的高效容错   弹性之三:Task如果失败会 ...

  5. 细谈getRequestDispatcher()与sendRedirect()的区别

    问题?细谈getRequestDispatcher()与sendRedirect()的区别 首先我们要知道: (1)request.getRequestDispatcher()是请求转发,前后页面共享 ...

  6. JAVA基础细谈

    JAVA基础细谈 一. 源文件和编译后的类文件     源文件的本质就是程序文件,是程序员编写,是人看的.而编译后的类文件是给电脑看的文件.一个类就是一个文件,无论这个类写在哪里,编译以后都是一个文件 ...

  7. Css的使用细谈

    Css的使用细谈 Css可以通过简单的更改CSS文件,改变网页的整体表现形式,可以减少我们的工作量,所以她是每一个网页设计人员的必修课. Css简介              (1) CSS是用于布局 ...

  8. 细谈HTML解析模块

     细谈HTML解析模块 Html在网页中所占的位置,用一个简单直观的图给展示一下:    

  9. 细谈unity资源管理的设计

    一.概要 本文主要说说Unity是如何管理的,基于何种方式,基于这种管理方式,又该如何规划资源管理,以及构建bundle,是后面需要详细讨论的. 二.Unity的资源管理方式 2.1 资源分类 uni ...

随机推荐

  1. 解决VS2008在win7找不到输入序列号的地方

    1.VS2008在Windows7 打开维护界面看不到可以输序列号的地方. 因为微软把他隐藏了. 2.我们可以借用工具把他显示出来 下载地址:http://www.zlsoft.com/techbbs ...

  2. WPF 捕获键盘输入事件

    最近修改的一个需求要求捕获键盘输入的 Text,包括各种标点符号. 最开始想到的是 PreviewKeyDown 或者 PreviewKeyUp 这样的键盘事件. 但是这两个事件的对象 KeyEven ...

  3. Android—自定义开关按钮实现

    我们在应用中经常看到一些选择开关状态的配置文件,做项目的时候用的是android的Switch控件,但是感觉好丑的样子………… 个人认为还是自定义的比较好,先上个效果图:

  4. class-dump 反编译私有的库和应用

    一.下载并安装class-dump 下载class-dump-3.5.dmg  点击下载 下载完成以后双击.dmg的文件,将里面的class-dump拷贝到/usr/local/bin 设置权限chm ...

  5. Block解析(iOS)

    1. 操作系统中的栈和堆 我们先来看看一个由C/C++/OBJC编译的程序占用内存分布的结构: 栈区(stack):由系统自动分配,一般存放函数参数值.局部变量的值等.由编译器自动创建与释放.其操作方 ...

  6. Tomcat 部署我的第一个程序

    idea 生成war包.先双击clean,再双击package.生成成功之后就会产生war包. 第二步:将生成好的war文件复制到tomcat文件夹下. 第三步:配置tomcat的server.xml ...

  7. linux下 lvm 磁盘扩容

    打算给系统装一个oracle,发现磁盘空间不足.在安装系统的时候我选择的是自动分区,系统就会自动以LVM的方式分区.为了保证系统后期的可用性,建议所有新系统安装都采用LVM,之后生产上的设备我也打算这 ...

  8. org.apache.ibatis.binding.BindingException: Invalid bound statement (not found):

    org.apache.ibatis.binding.BindingException: Invalid bound statement (not found): da.huying.usermanag ...

  9. Xamarin中使用DatePickerDialog的相关问题

    在Xamarin中在使用Datepicker的时候,一般情况下只需要在对应的按钮或其他控件的点击事件中使用如下语句即可完成: EditText etBirthday = FindViewById< ...

  10. 【腾讯Bugly干货分享】WebVR如此近-three.js的WebVR示例解析

    本文来自于腾讯bugly开发者社区,非经作者同意,请勿转载,原文地址:http://dev.qq.com/topic/57c7ff1689a6c9121b1adb16 作者:苏晏烨 关于WebVR 最 ...