在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. Windows API 设置窗口下控件Enable属性

    参考页面: http://www.yuanjiaocheng.net/webapi/create-crud-api-1-put.html http://www.yuanjiaocheng.net/we ...

  2. ASP.NET MVC原理

    仅此一文让你明白ASP.NET MVC原理   ASP.NET MVC由以下两个核心组成部分构成: 一个名为UrlRoutingModule的自定义HttpModule,用来解析Controller与 ...

  3. Android—关于自定义对话框的工具类

    开发中有很多地方会用到自定义对话框,为了避免不必要的城府代码,在此总结出一个工具类. 弹出对话框的地方很多,但是都大同小异,不同无非就是提示内容或者图片不同,下面这个类是将提示内容和图片放到了自定义函 ...

  4. Android开发案例 – 在AbsListView中使用倒计时

    在App中, 有多种多样的倒计时需求, 比如: 在单View上, 使用倒计时, 如(如图-1) 在ListView(或者GridView)的ItemView上, 使用倒计时(如图-2) 图-1 图-2 ...

  5. Linux.NET实战手记—自己动手改泥鳅(上)

    各位读者大家好,不知各位读者有否阅读在下的前一个系列<Linux.NET 学习手记>,在前一个系列中,我们从Linux中Mono的编译安装开始,到Jexus服务器的介绍,以及如何在Linu ...

  6. 你从未知道如此强大的ASP.NET MVC DefaultModelBinder

    看到很多ASP.NET MVC项目还在从request.querystring或者formContext里面获取数据,这实在是非常落后的做法.也有的项目建了大量的自定义的modelbinder,以为很 ...

  7. Hadoop学习笔记系列文章导航

    一.为何要学习Hadoop? 这是一个信息爆炸的时代.经过数十年的积累,很多企业都聚集了大量的数据.这些数据也是企业的核心财富之一,怎样从累积的数据里寻找价值,变废为宝炼数成金成为当务之急.但数据增长 ...

  8. useful commands for docker beginner

    You may want to add my wechat public account or add my technical blog's RSS feed This list is meant ...

  9. Entity Framework 6 Recipes 2nd Edition(11-2)译 -> 用”模型定义”函数过滤实体集

    11-2. 用”模型定义”函数过滤实体集 问题 想要创建一个”模型定义”函数来过滤一个实体集 解决方案 假设我们已有一个客户(Customer)和票据Invoice)模型,如Figure 11-2所示 ...

  10. Atitit 图像处理的心得与疑惑 attilax总结

    Atitit 图像处理的心得与疑惑 attilax总结 1.1. 使用类库好不好??还是自己实现算法1 1.2. 但是,如果遇到类库体积太大,后者没有合适的算法,那就只能自己开发算法了1 1.3. 如 ...