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 条评论
登录 后参与评论

相关文章

来自专栏HTML5学堂

操作符与数据类型转换

上一期堡堡给大家讲解了关于JS的基础语法,虽然是一些非常基础的知识,但是它对大家的后期学习奠定了一定的基础。知识像一张网,基础越扎实,网住的鱼就越多,要告诉大家...

2808
来自专栏杨龙飞前端

编写一个javscript函数 fn,该函数有一个参数 n(数字类型),其返回值是一个数组,该数组内是 n 个随机且不重复的整数,且整数取值范围是 [2, 32]。

1895
来自专栏jojo的技术小屋

原 三、基本概念

作者:汪娇娇 时间:2017年11月4日 一、语法 1、区分大小写 2、标识符 指变量、函数、属性的名字,采用驼峰大小写格式。 3、注释 单行:// 多行:/*...

2555
来自专栏web前端-

Date对象和Math对象

    1.  var  date=new Date()    //无参数的情况下返回值为当前时间

775
来自专栏较真的前端

关于数字的前端面试题

2576
来自专栏柠檬先生

Sass 基础(六)

join() 函数    join()函数是将两个列表连接合并成一个列表。    >>join(10px 20px, 30px 40px)       (...

19210
来自专栏娱乐心理测试

JS各种基本数据类型常用方法总结(看这篇就够了)

所有语言的基本数据类型就是那么几种,因为之前一直从事移动端,在学习js的过程中,总结一下js的基本类型及用法。

633
来自专栏跟着阿笨一起玩NET

SQL Server数据库获取TEXT字段的内容长度的方法

SQL Server数据库如何获取TEXT字段的内容长度呢?本文我们就来介绍一下SQL Server数据库如何获取TEXT字段的内容长度的方法,是通过DATA...

593
来自专栏从零开始学 Web 前端

从零开始学 Web 之 ES6(六)ES6基础语法四

在这里我会从 Web 前端零基础开始,一步步学习 Web 相关的知识点,期间也会分享一些好玩的项目。现在就让我们一起进入 Web 前端学习的冒险之旅吧!

754
来自专栏技术沉淀

Python: collections模块实例透析Collections模块

1678

扫码关注云+社区