在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. ecshop验证码

    <?php //仿制ecshop验证码(四位大写字母和数字.背景) //处理码值(四位大写字母和数字组成) //所有的可能的字符集合 $chars = 'ABCDEFGHIJKLMNOPQRST ...

  2. Tomcat常见问题及常用命令

    很长时间不用tomcat好多命令都忘记了,所以准备自己记录下来,以便参考.刚好也希望可以开始养成记博客的好习惯. 1.查看java的版本号 进入java的安装目录后,使用命令:java -versio ...

  3. Atiti.大企业病与小企业病 大公司病与小公司病

    Atiti.大企业病与小企业病 大公司病与小公司病 1. 大企业病,一般会符合机构臃肿 .多重领导 .人才流失的特点.1 2. 大企业病避免方法1 3. 小企业病 1 3.1.1. 表现1 4. 如何 ...

  4. Struts的文件上传下载

    Struts的文件上传下载 1.文件上传 Struts2的文件上传也是使用fileUpload的组件,这个组默认是集合在框架里面的.且是使用拦截器:<interceptor name=" ...

  5. Linux下部署ASP.NET服务连接oracle遇到的问题记录

    一.如何卸载MONO Q:mono是linux系统上跨平台软件,卸载它有两种方式: 1.知道mono安装路径,安装原来的路径直接覆盖安装(最为简单): 2.不知道mono安装路径,首先通过sudo f ...

  6. EasyPR--开发详解(4)形态学操作、尺寸验证、旋转等操作

    在上一篇深度分析与调优讨论中,我们介绍了高斯模糊,灰度化和Sobel算子.在本文中,会分析剩余的定位步骤. 根据前文的内容,车牌定位的功能还剩下如下的步骤,见下图中未涂灰的部分. 图1 车牌定位步骤 ...

  7. 周六搞事情,微信小程序开发文档已放出!

    程序员们,你们有事干了! 个人感觉不管是什么形式的UI技术,都无法决定一个产品的生死,核心还是服务和模式的创新. 某些方面和ApiCloud好像,但发展前景远远胜过ApiCloud. 微信小程序可以为 ...

  8. ABP源码分析二十:ApplicationService

    IApplicationService : 空接口,起标识作用.所有实现了IApplicationService 的类都会被自动注入到容器中.同时所有IApplicationService对象都会被注 ...

  9. Atitit 输入法原理与概论ati use

    Atitit 输入法原理与概论ati use 1.1. 输入法技术点1 1.2. 参考多多输入法设置2 1.3. Attilax博客集合知识点2 1.4. 输入法的书籍当当几乎没有..都是打字的.2 ...

  10. SqlService过期的解决方案

    看图吧,不喜欢说话,图里面我都打备注了 0SQLService异常 1找到安装中心 2升级版本 3监测ing 4输入升级key 5同意并下一步 6下一步 7下一步 8下一步 9收工 10可以打开了