FunDA(1)- Query Result Row:强类型Query结果行

    FunDA的特点之一是以数据流方式提供逐行数据操作支持。这项功能解决了FRM如Slick数据操作以SQL批次模式为主所产生的问题。为了实现安全高效的数据行操作,我们必须把FRM产生的Query结果集转变成一种强类型的结果集,也就是可以字段名称进行操作的数据行类型结果集。在前面的一篇讨论中我们介绍了通过Shape来改变Slick Query结果行类型。不过这样的转变方式需要编程人员对Slick有较深的了解。更重要的是这种方式太依赖Slick的内部功能了。我们希望FunDA可以支持多种FRM,所以应当尽量避免与任何FRM的紧密耦合。看来从FRM的返回结果开始进行数据行类型格式转换是一种比较现实的选择。一般来说我们还是可以假定任何FRM的使用者对于FRM的Query结果集类型是能理解的,因为他们的主要目的就是为了使用这个结果集。那么由FunDA的使用者提供一个Query结果数据行与另一种类型的类型转换函数应该不算是什么太高的要求吧。FunDA的设计思路是由用户提供一个目标类型以及FRM Query结果数据行到这个强类型行类型的类型转换函数后由FunDA提供强类型行结果集。下面先看一个典型的Slick Query例子:

 1 import slick.driver.H2Driver.api._
 2 import scala.concurrent.duration._
 3 import scala.concurrent.Await
 4 
 5 object TypedRow extends App {
 6 
 7   class AlbumsTable(tag: Tag) extends Table[
 8     (Long,String,String,Option[Int],Int)](tag,"ALBUMS") {
 9     def id = column[Long]("ID",O.PrimaryKey)
10     def title = column[String]("TITLE")
11     def artist = column[String]("ARTIST")
12     def year = column[Option[Int]]("YEAR")
13     def company = column[Int]("COMPANY")
14     def * = (id,title,artist,year,company)
15   }
16   val albums = TableQuery[AlbumsTable]
17   class CompanyTable(tag: Tag) extends Table[(Int,String)](tag,"COMPANY") {
18     def id = column[Int]("ID",O.PrimaryKey)
19     def name = column[String]("NAME")
20     def * = (id, name)
21   }
22   val companies = TableQuery[CompanyTable]
23 
24   val albumInfo = for {
25     a <- albums
26     c <- companies
27     if (a.company === c.id)
28   } yield(a.title,a.artist,a.year,c.name)
29 
30   val db = Database.forConfig("h2db")
31 
32   Await.result(db.run(albumInfo.result),Duration.Inf).foreach {r =>
33     println(s"${r._1} by ${r._2}, ${r._3.getOrElse(2000)} ${r._4}")
34   }
35   
36 }

上面例子里的albumInfo返回结果行类型是个Tuple类型:(String,String,Option[Int],Int),没有字段名的,所以只能用r._1,r._2...这样的位置注明方式来选择字段。用这种形式来使用返回结果很容易造成混乱,选用字段错误。

前面提到:如果用户能提供一个返回行类型和一个转换函数如下:

1   case class AlbumRow(title: String,artist: String,year: Int,studio: String)
2   def toTypedRow(raw: (String,String,Option[Int],String)):AlbumRow =
3     AlbumRow(raw._1,raw._2,raw._3.getOrElse(2000),raw._4)

我们可以在读取数据后用这个函数来转换行类型:

1   Await.result(db.run(albumInfo.result),Duration.Inf).map{raw =>
2     toTypedRow(raw)}.foreach {r =>
3     println(s"${r.title} by ${r.artist}, ${r.year} ${r.studio}")
4   }

返回行类型AlbumRow是个强类型。现在我吗可以用字段名来选择数据字段值了。不过,还是有些地方不对劲:应该是用户提供了目标行类型和转换函数后,直接调用一个函数就可以得到需要的结果集了。是的,我们就是要设计一套后台工具库来提供这个函数。

下面我们要设计FunDA的数据行类型class FDADataRow。这个类型现在基本上完全是针对Slick而设的,成功完成功能实现后期再考虑松散耦合问题。这个类型需要一个目标行类型定义和一个类型转换函数,外加一些Slick profile, database等信息。然后提供一个目标行类型结果集函数getTypedRows:

package com.bayakala.funda.rowtypes

import scala.concurrent.duration._
import scala.concurrent.Await
import slick.driver.JdbcProfile

object DataRowType {
  class FDADataRow[SOURCE, TARGET](slickProfile: JdbcProfile,convert: SOURCE  => TARGET){
    import slickProfile.api._

    def getTypedRows(slickAction: DBIO[Iterable[SOURCE]])(slickDB: Database): Iterable[TARGET] =
      Await.result(slickDB.run(slickAction), Duration.Inf).map(raw => convert(raw))
  }

  object FDADataRow {
    def apply[SOURCE, TARGET](slickProfile: JdbcProfile, converter: SOURCE => TARGET): FDADataRow[SOURCE, TARGET] =
      new FDADataRow[SOURCE, TARGET](slickProfile, converter)
  }

}

下面是这个函数库的使用示范:

1   import com.bayakala.funda.rowtypes.DataRowType
2 
3   val loader = FDADataRow(slick.driver.H2Driver, toTypedRow _)
4 
5   loader.getTypedRows(albumInfo.result)(db).foreach {r =>
6     println(s"${r.title} by ${r.artist}, ${r.year} ${r.studio}")
7   }

那么,作为一种数据行,又如何进行数据字段的更新呢?我们应该把它当作immutable object用函数式方法更新:

1   def updateYear(from: AlbumRow): AlbumRow =
2     AlbumRow(from.title,from.artist,from.year+1,from.studio)
3 
4   loader.getTypedRows(albumInfo.result)(db).map(updateYear).foreach {r =>
5     println(s"${r.title} by ${r.artist}, ${r.year} ${r.studio}")
6   }

updateYear是个典型的函数式方法:传入AlbumRow,返回新的AlbumRow。

下面是这篇讨论中的源代码:

FunDA函数库:

 1 package com.bayakala.funda.rowtypes
 2 
 3 import scala.concurrent.duration._
 4 import scala.concurrent.Await
 5 import slick.driver.JdbcProfile
 6 
 7 object DataRowType {
 8   class FDADataRow[SOURCE, TARGET](slickProfile: JdbcProfile,convert: SOURCE  => TARGET){
 9     import slickProfile.api._
10 
11     def getTypedRows(slickAction: DBIO[Iterable[SOURCE]])(slickDB: Database): Iterable[TARGET] =
12       Await.result(slickDB.run(slickAction), Duration.Inf).map(raw => convert(raw))
13   }
14 
15   object FDADataRow {
16     def apply[SOURCE, TARGET](slickProfile: JdbcProfile, converter: SOURCE => TARGET): FDADataRow[SOURCE, TARGET] =
17       new FDADataRow[SOURCE, TARGET](slickProfile, converter)
18   }
19 
20 }

功能测试源代码:

 1 import slick.driver.H2Driver.api._
 2 
 3 import scala.concurrent.duration._
 4 import scala.concurrent.Await
 5 
 6 object TypedRow extends App {
 7 
 8   class AlbumsTable(tag: Tag) extends Table[
 9     (Long,String,String,Option[Int],Int)](tag,"ALBUMS") {
10     def id = column[Long]("ID",O.PrimaryKey)
11     def title = column[String]("TITLE")
12     def artist = column[String]("ARTIST")
13     def year = column[Option[Int]]("YEAR")
14     def company = column[Int]("COMPANY")
15     def * = (id,title,artist,year,company)
16   }
17   val albums = TableQuery[AlbumsTable]
18   class CompanyTable(tag: Tag) extends Table[(Int,String)](tag,"COMPANY") {
19     def id = column[Int]("ID",O.PrimaryKey)
20     def name = column[String]("NAME")
21     def * = (id, name)
22   }
23   val companies = TableQuery[CompanyTable]
24 
25   val albumInfo =
26     for {
27       a <- albums
28       c <- companies
29       if (a.company === c.id)
30     } yield(a.title,a.artist,a.year,c.name)
31 
32   val db = Database.forConfig("h2db")
33 
34   Await.result(db.run(albumInfo.result),Duration.Inf).foreach {r =>
35     println(s"${r._1} by ${r._2}, ${r._3.getOrElse(2000)} ${r._4}")
36   }
37 
38   case class AlbumRow(title: String,artist: String,year: Int,studio: String)
39   def toTypedRow(raw: (String,String,Option[Int],String)):AlbumRow =
40     AlbumRow(raw._1,raw._2,raw._3.getOrElse(2000),raw._4)
41 
42   Await.result(db.run(albumInfo.result),Duration.Inf).map{raw =>
43     toTypedRow(raw)}.foreach {r =>
44     println(s"${r.title} by ${r.artist}, ${r.year} ${r.studio}")
45   }
46 
47   import com.bayakala.funda.rowtypes.DataRowType.FDADataRow
48 
49   val loader = FDADataRow(slick.driver.H2Driver, toTypedRow _)
50 
51   loader.getTypedRows(albumInfo.result)(db).foreach {r =>
52     println(s"${r.title} by ${r.artist}, ${r.year} ${r.studio}")
53   }
54 
55   def updateYear(from: AlbumRow): AlbumRow =
56     AlbumRow(from.title,from.artist,from.year+1,from.studio)
57 
58   loader.getTypedRows(albumInfo.result)(db).map(updateYear).foreach {r =>
59     println(s"${r.title} by ${r.artist}, ${r.year} ${r.studio}")
60   }
61 
62 }

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏F_Alex

(四)SpringBoot2.0基础篇- 多数据源,JdbcTemplate和JpaRepository

1524
来自专栏技术博客

一步一步学Linq to sql(二):DataContext与实体

 DataContext类型(数据上下文)是System.Data.Linq命名空间下的重要类型,用于把查询句法翻译成SQL语句,以及把数据从数据库返回给调用方...

712
来自专栏码匠的流水账

聊聊lettuce的sentinel连接

lettuce-core-5.0.4.RELEASE-sources.jar!/io/lettuce/core/RedisClient.java

602
来自专栏Android干货

安卓开发_数据存储技术_sqlite

2587
来自专栏个人分享

Hive metastore整体代码分析及详解

  从上一篇对Hive metastore表结构的简要分析中,我再根据数据设计的实体对象,再进行整个代码结构的总结。那么我们先打开metadata的目录,其目录...

1123
来自专栏菩提树下的杨过

[转自JeffreyZhao]在LINQ to SQL中使用Translate方法以及修改查询用SQL

目前LINQ to SQL的资料不多——老赵的意思是,目前能找到的资料都难以摆脱“官方用法”的“阴影”。LINQ to SQL最权威的资料自然是MSDN,但是M...

1895
来自专栏Jerry的SAP技术分享

如何在Java代码中使用SAP云平台CloudFoundry环境的环境变量

在我的公众号文章在SAP云平台的CloudFoundry环境下消费ABAP On-Premise OData服务介绍了如何通过Cloud Connector连接...

1475
来自专栏wannshan(javaer,RPC)

Spring 事件传播机制分析

Spring4.0.4 Spring 事件机制采用的是观察者模型(又叫订阅发布模型) 有以下角色 ? 这里,观察者就是监听者,被观察者就是事件对象。 事件发布流...

31810
来自专栏函数式编程语言及工具

ScalaPB(2): 在scala中用gRPC实现微服务

1563
来自专栏Spark学习技巧

Spark源码系列之spark2.2的StructuredStreaming使用及源码介绍

一,概述 Structured Streaming是一个可扩展和容错的流处理引擎,并且是构建于sparksql引擎之上。你可以用处理静态数据的方式去处理你的流计...

6616

扫码关注云+社区