细谈Slick(6)- Projection:ProvenShape,强类型的Query结果类型

2024-04-09 04:58

本文主要是介绍细谈Slick(6)- Projection:ProvenShape,强类型的Query结果类型,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

  在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: Anyval 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 = vval 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 classcase class Pair[A, B](a: A, b: B)// A Shape implementation for Pairfinal 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(0), elems(1))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 definitionclass 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 {@inlineimplicit 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)@inlineimplicit 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.createAwait.ready(db.run(createSchemaAction),Duration.Inf)val initDataAction = DBIO.seq {tupleTypedPerson ++= Seq((Some(0),"Tiger Chan", 45, Some("Tiger_XC")),(Some(0),"Johnny Cox", 17, None),(Some(0),"Cathy Williams", 18, Some("Catty")),(Some(0),"David Wong", 43, None))}Await.ready(db.run(initDataAction),Duration.Inf)val queryAction = tupleTypedPerson.resultAwait.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(0),hl(1),hl(2),hl(3))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.createAwait.ready(db.run(createTitleAction),Duration.Inf)val initTitleData = DBIO.seq {personTitle ++= Seq(Title(1,"Manager"),Title(2,"Programmer"),Title(3,"Clerk"))}Await.ready(db.run(initTitleData),Duration.Inf)case class Titles(id: Int, name: String, title: String)val qPersonWithTitle = for {p <- hlistPersont <- 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.createAwait.ready(db.run(createSchemaAction),Duration.Inf)val initDataAction = DBIO.seq {tupleTypedPerson ++= Seq((Some(0),"Tiger Chan", 45, Some("Tiger_XC")),(Some(0),"Johnny Cox", 17, None),(Some(0),"Cathy Williams", 18, Some("Catty")),(Some(0),"David Wong", 43, None))}Await.ready(db.run(initDataAction),Duration.Inf)val queryAction = tupleTypedPerson.resultAwait.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(0),hl(1),hl(2),hl(3))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.createAwait.ready(db.run(createTitleAction),Duration.Inf)val initTitleData = DBIO.seq {personTitle ++= Seq(Title(1,"Manager"),Title(2,"Programmer"),Title(3,"Clerk"))}Await.ready(db.run(initTitleData),Duration.Inf)case class Titles(id: Int, name: String, title: String)val qPersonWithTitle = for {p <- hlistPersont <- 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结果类型的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/887228

相关文章

零基础学习Redis(10) -- zset类型命令使用

zset是有序集合,内部除了存储元素外,还会存储一个score,存储在zset中的元素会按照score的大小升序排列,不同元素的score可以重复,score相同的元素会按照元素的字典序排列。 1. zset常用命令 1.1 zadd  zadd key [NX | XX] [GT | LT]   [CH] [INCR] score member [score member ...]

自定义类型:结构体(续)

目录 一. 结构体的内存对齐 1.1 为什么存在内存对齐? 1.2 修改默认对齐数 二. 结构体传参 三. 结构体实现位段 一. 结构体的内存对齐 在前面的文章里我们已经讲过一部分的内存对齐的知识,并举出了两个例子,我们再举出两个例子继续说明: struct S3{double a;int b;char c;};int mian(){printf("%zd\n",s

【编程底层思考】垃圾收集机制,GC算法,垃圾收集器类型概述

Java的垃圾收集(Garbage Collection,GC)机制是Java语言的一大特色,它负责自动管理内存的回收,释放不再使用的对象所占用的内存。以下是对Java垃圾收集机制的详细介绍: 一、垃圾收集机制概述: 对象存活判断:垃圾收集器定期检查堆内存中的对象,判断哪些对象是“垃圾”,即不再被任何引用链直接或间接引用的对象。内存回收:将判断为垃圾的对象占用的内存进行回收,以便重新使用。

flume系列之:查看flume系统日志、查看统计flume日志类型、查看flume日志

遍历指定目录下多个文件查找指定内容 服务器系统日志会记录flume相关日志 cat /var/log/messages |grep -i oom 查找系统日志中关于flume的指定日志 import osdef search_string_in_files(directory, search_string):count = 0

两个月冲刺软考——访问位与修改位的题型(淘汰哪一页);内聚的类型;关于码制的知识点;地址映射的相关内容

1.访问位与修改位的题型(淘汰哪一页) 访问位:为1时表示在内存期间被访问过,为0时表示未被访问;修改位:为1时表示该页面自从被装入内存后被修改过,为0时表示未修改过。 置换页面时,最先置换访问位和修改位为00的,其次是01(没被访问但被修改过)的,之后是10(被访问了但没被修改过),最后是11。 2.内聚的类型 功能内聚:完成一个单一功能,各个部分协同工作,缺一不可。 顺序内聚:

Mysql BLOB类型介绍

BLOB类型的字段用于存储二进制数据 在MySQL中,BLOB类型,包括:TinyBlob、Blob、MediumBlob、LongBlob,这几个类型之间的唯一区别是在存储的大小不同。 TinyBlob 最大 255 Blob 最大 65K MediumBlob 最大 16M LongBlob 最大 4G

Oracle type (自定义类型的使用)

oracle - type   type定义: oracle中自定义数据类型 oracle中有基本的数据类型,如number,varchar2,date,numeric,float....但有时候我们需要特殊的格式, 如将name定义为(firstname,lastname)的形式,我们想把这个作为一个表的一列看待,这时候就要我们自己定义一个数据类型 格式 :create or repla

MyBatis 切换不同的类型数据库方案

下属案例例当前结合SpringBoot 配置进行讲解。 背景: 实现一个工程里面在部署阶段支持切换不同类型数据库支持。 方案一 数据源配置 关键代码(是什么数据库,该怎么配就怎么配) spring:datasource:name: test# 使用druid数据源type: com.alibaba.druid.pool.DruidDataSource# @需要修改 数据库连接及驱动u

按揭贷款类型

按揭贷款可以根据不同的分类标准分为多种类型。以下是按揭贷款的一些常见分类: 按贷款利率分类: 固定利率按揭(Fixed Rate Mortgage, FRM):在整个贷款期间,利率保持不变,这意味着每月还款额也是固定的。浮动利率按揭(Adjustable Rate Mortgage, ARM):贷款利率随市场利率的变化而调整,通常有一个基准利率加上一定的浮动点数。 按还款方式分类: 等额本息(

PHP7扩展开发之类型处理

前言 这次,我们将演示如何在PHP扩展中如何对类型进行一些操作。如,判断变量类型。要实现的PHP代码如下: <?phpfunction get_size ($value) {if (is_string($value)) {return "string size is ". strlen($value);} else if (is_array($value)) {return "array si