在上一集的讨论里我们介绍并实现了强类型返回结果行。使用强类型主要的目的是当我们把后端数据库SQL批次操作搬到内存里转变成数据流式按行操作时能更方便、准确、高效地选定数据字段。在上集讨论示范里我们用集合的foreach方式模拟了一个最简单的数据流,并把从数据库里批次读取的数据集转换成一串连续的数据行来逐行使用。一般来说完整的流式数据处理流程包括了从数据库中读取数据、根据读取的每行数据状态再对后台数据库进行更新,包括:插入新数据、更新、删除等。那么在上篇中实现的流式操作基础上再添加一种指令行类型就可以完善整个数据处理流程了,就像下面这个图示:

Database => Query -> Collection => Streaming -> DataRow => QueryAction(DataRow) -> ActionRow => execAction(ActionRow) -> Database 

如果我们还是以Slick为目标FRM,那么这个ActionRow的类型就是Slick的DBIO[T]了:

 package com.bayakala.funda.rowtypes
import slick.dbio._
object ActionType {
type FDAAction[T] = DBIO[T]
}

记得有一次在一个Scala讨论区里遇到这样一个问题:如何把a表里的status字段更新成b表的status字段值,转化成SQL语句如下:

 update a,b set a.status=b.status where a.id=b.id

那位哥们的问题是如何用Slick来实现对a表的更新,不能用sql"???" interpolation 直接调用SQL语句,可能因为要求compile time语法check保障吧。这个问题用Slick Query还真的不太容易解决(能不能解决就不想费功夫去想了),这是因为FRM的SQL批次处理弱点。如果用FunDA的流式操作思路就会很容易解决了,只要用join Query把b.status读出来再用b.id=a.id逐个更新a.status。刚好,下面我们就示范通过ActionRow来解决这个问题。先用下面这段代码来设置测试数据:

 import slick.dbio.DBIO
import slick.driver.H2Driver.api._ import scala.concurrent.duration._
import scala.concurrent.{Await, Future}
import scala.util.{Failure, Success}
import scala.concurrent.ExecutionContext.Implicits.global
import slick.jdbc.meta.MTable
object ActionRowTest extends App { class ATable(tag: Tag) extends Table[(Int,String,Int)](tag,"TA") {
def id = column[Int]("id",O.PrimaryKey)
def flds = column[String]("aflds")
def status = column[Int]("status")
def * = (id,flds,status)
}
val tableA = TableQuery[ATable] class BTable(tag: Tag) extends Table[(Int,String,Int)](tag,"TB") {
def id = column[Int]("id",O.PrimaryKey)
def flds = column[String]("bflds")
def status = column[Int]("status")
def * = (id,flds,status)
}
val tableB = TableQuery[BTable] val insertAAction =
tableA ++= Seq (
(,"aaa",),
(,"bbb",),
(,"ccc",),
(,"ddd",),
(,"kkk",)
)
val insertBAction =
tableB ++= Seq (
(,"aaa",),
(,"bbb",),
(,"ccc",),
(,"ddd",),
(,"kkk",)
) val db = Database.forConfig("h2db") def tableExists(tables: Vector[MTable], tblname: String) =
tables.exists {t =>t.name.toString.contains(tblname)} def createSchemaIfNotExists(): Future[Unit] = {
db.run(MTable.getTables).flatMap {
case tables if !tableExists(tables,".TA") && !tableExists(tables,".TB") =>
println("Creating schemas for TA and TB...")
db.run((tableA.schema ++ tableB.schema).create)
case tables if !tableExists(tables,".TA") =>
println("Creating schema for TA ...")
db.run(tableA.schema.create)
case tables if !tableExists(tables,".TB") =>
println("Creating schema for TB ...")
db.run(tableB.schema.create)
case _ =>
println("Schema for TA, TB already created.")
Future.successful()
}
} def insertInitialData(): Future[Unit] = {
val cleanInsert = DBIO.seq(
tableA.delete, tableB.delete,
insertAAction,
insertBAction)
db.run(cleanInsert).andThen {
case Success(_) => println("Data insert completed.")
case Failure(e) => println(s"Data insert failed [${e.getMessage}]")
}
} Await.ready(db.run(sql"DROP TABLE TA; DROP TABLE TB".as[String]),Duration.Inf) val initResult = createSchemaIfNotExists().flatMap {_ => insertInitialData()}
Await.ready(initResult,Duration.Inf) }

用join query先把这两个表相关的字段值搬到内存转成强类型行FDADataRow:

 val selectAB = for {
a <- tableA
b <- tableB
if (a.id === b.id)
} yield (a.id,b.id,a.status,b.status) case class ABRow (id: Int, asts: Int, bsts: Int)
def toABRow(raw: (Int,Int,Int,Int)) = ABRow(raw._1,raw._3,raw._4) import com.bayakala.funda.rowtypes.DataRowType val loader = FDADataRow(slick.driver.H2Driver, toABRow _)
loader.getTypedRows(selectAB.result)(db).foreach {dataRow =>
println(s"ID:${dataRow.id} Status A = ${dataRow.asts}, B = ${dataRow.bsts}")
}

初始结果如下:

ID: Status A = , B =
ID: Status A = , B =
ID: Status A = , B =
ID: Status A = , B =

现在我们把每条数据行DataRow转成动作行ActionRow。然后把每条DataRow的asts字段值替换成bsts的字段值:

 import com.bayakala.funda.rowtypes.ActionType.FDAAction
def updateAStatus(row: ABRow): FDAAction[Int] = {
tableA.filter{r => r.id === row.id}
.map(_.status)
.update(row.asts)
} loader.getTypedRows(selectAB.result)(db).map(updateAStatus(_)).foreach {
actionRow =>
println(s"${actionRow.toString}")
}

显示结果如下:

slick.driver.JdbcActionComponent$UpdateActionExtensionMethodsImpl$$anon$@492691d7
slick.driver.JdbcActionComponent$UpdateActionExtensionMethodsImpl$$anon$@27216cd
slick.driver.JdbcActionComponent$UpdateActionExtensionMethodsImpl$$anon$@558bdf1f
slick.driver.JdbcActionComponent$UpdateActionExtensionMethodsImpl$$anon$@8576fa0

现在每条DataRow已经被转化成jdbc action类型了。

下一步我们只需要运行这些ActionRow就可以完成任务了:

   def execAction(act: FDAAction[Int]) = db.run(act)

    loader.getTypedRows(selectAB.result)(db)
.map(updateAStatus(_))
.map(execAction(_))

现在再看看数据库中的TA表状态:

  loader.getTypedRows(selectAB.result)(db).foreach {dataRow =>
println(s"ID:${dataRow.id} Status A = ${dataRow.asts}, B = ${dataRow.bsts}")
} 结果:
ID: Status A = , B =
ID: Status A = , B =
ID: Status A = , B =
ID: Status A = , B =

我们看到已经正确更新了TA的status字段值。

在这个示范中明显有很多不足之处:如果a.status=b.status应该省略更新步骤。这是因为foreach只能模拟最基本的数据流动。如果我们使用了具备强大功能的Stream工具库如scalaz-stream-fs2,就可以更好控制数据元素的流动。更重要的是scalaz-stream-fs2支持并行运算,那么上面所描述的流程:

Database => Query -> Collection => Streaming -> DataRow => QueryAction(DataRow) -> ActionRow => execAction(ActionRow) -> Database

几个 => 环节:Query、Streaming、QueryAction、execAction将可以并行运算,从而实现充分利用多核CPU硬件资源,提高运算效率的目的。

下面是这次讨论涉及的源代码:

 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)
} }
 package com.bayakala.funda.rowtypes
import slick.dbio._
object ActionType {
type FDAAction[T] = DBIO[T]
}
 import slick.dbio.DBIO
import slick.driver.H2Driver.api._ import scala.concurrent.duration._
import scala.concurrent.{Await, Future}
import scala.util.{Failure, Success}
import scala.concurrent.ExecutionContext.Implicits.global
import slick.jdbc.meta.MTable
object ActionRowTest extends App { class ATable(tag: Tag) extends Table[(Int,String,Int)](tag,"TA") {
def id = column[Int]("id",O.PrimaryKey)
def flds = column[String]("aflds")
def status = column[Int]("status")
def * = (id,flds,status)
}
val tableA = TableQuery[ATable] class BTable(tag: Tag) extends Table[(Int,String,Int)](tag,"TB") {
def id = column[Int]("id",O.PrimaryKey)
def flds = column[String]("bflds")
def status = column[Int]("status")
def * = (id,flds,status)
}
val tableB = TableQuery[BTable] val insertAAction =
tableA ++= Seq (
(,"aaa",),
(,"bbb",),
(,"ccc",),
(,"ddd",),
(,"kkk",)
)
val insertBAction =
tableB ++= Seq (
(,"aaa",),
(,"bbb",),
(,"ccc",),
(,"ddd",),
(,"kkk",)
) val db = Database.forConfig("h2db") def tableExists(tables: Vector[MTable], tblname: String) =
tables.exists {t =>t.name.toString.contains(tblname)} def createSchemaIfNotExists(): Future[Unit] = {
db.run(MTable.getTables).flatMap {
case tables if !tableExists(tables,".TA") && !tableExists(tables,".TB") =>
println("Creating schemas for TA and TB...")
db.run((tableA.schema ++ tableB.schema).create)
case tables if !tableExists(tables,".TA") =>
println("Creating schema for TA ...")
db.run(tableA.schema.create)
case tables if !tableExists(tables,".TB") =>
println("Creating schema for TB ...")
db.run(tableB.schema.create)
case _ =>
println("Schema for TA, TB already created.")
Future.successful()
}
} def insertInitialData(): Future[Unit] = {
val cleanInsert = DBIO.seq(
tableA.delete, tableB.delete,
insertAAction,
insertBAction)
db.run(cleanInsert).andThen {
case Success(_) => println("Data insert completed.")
case Failure(e) => println(s"Data insert failed [${e.getMessage}]")
}
} Await.ready(db.run(sql"DROP TABLE TA; DROP TABLE TB".as[String]),Duration.Inf) val initResult = createSchemaIfNotExists().flatMap {_ => insertInitialData()}
Await.ready(initResult,Duration.Inf) val selectAB = for {
a <- tableA
b <- tableB
if (a.id === b.id)
} yield (a.id,b.id,a.status,b.status) case class ABRow (id: Int, asts: Int, bsts: Int)
def toABRow(raw: (Int,Int,Int,Int)) = ABRow(raw._1,raw._3,raw._4) import com.bayakala.funda.rowtypes.DataRowType.FDADataRow val loader = FDADataRow(slick.driver.H2Driver, toABRow _)
loader.getTypedRows(selectAB.result)(db).foreach {dataRow =>
println(s"ID:${dataRow.id} Status A = ${dataRow.asts}, B = ${dataRow.bsts}")
} import com.bayakala.funda.rowtypes.ActionType.FDAAction
def updateAStatus(row: ABRow): FDAAction[Int] = {
tableA.filter{r => r.id === row.id}
.map(_.status)
.update(row.bsts)
} loader.getTypedRows(selectAB.result)(db).map(updateAStatus(_)).foreach {
actionRow =>
println(s"${actionRow.toString}")
} def execAction(act: FDAAction[Int]) = db.run(act) loader.getTypedRows(selectAB.result)(db)
.map(updateAStatus(_))
.map(execAction(_)) loader.getTypedRows(selectAB.result)(db).foreach {dataRow =>
println(s"ID:${dataRow.id} Status A = ${dataRow.asts}, B = ${dataRow.bsts}")
} }

FunDA(2)- Streaming Data Operation:流式数据操作的更多相关文章

  1. Spark Streaming:大规模流式数据处理的新贵(转)

    原文链接:Spark Streaming:大规模流式数据处理的新贵 摘要:Spark Streaming是大规模流式数据处理的新贵,将流式计算分解成一系列短小的批处理作业.本文阐释了Spark Str ...

  2. Spark Streaming:大规模流式数据处理的新贵

    转自:http://www.csdn.net/article/2014-01-28/2818282-Spark-Streaming-big-data 提到Spark Streaming,我们不得不说一 ...

  3. 翻译-In-Stream Big Data Processing 流式大数据处理

    相当长一段时间以来,大数据社区已经普遍认识到了批量数据处理的不足.很多应用都对实时查询和流式处理产生了迫切需求.最近几年,在这个理念的推动下,催生出了一系列解决方案,Twitter Storm,Yah ...

  4. 字节跳动流式数据集成基于Flink Checkpoint两阶段提交的实践和优化

    背景 字节跳动开发套件数据集成团队(DTS ,Data Transmission Service)在字节跳动内基于 Flink 实现了流批一体的数据集成服务.其中一个典型场景是 Kafka/ByteM ...

  5. Hadoop_11_HDFS的流式 API 操作

    对于MapReduce等框架来说,需要有一套更底层的API来获取某个指定文件中的一部分数据,而不是一整个文件 因此使用流的方式来操作 HDFS上的文件,可以实现读取指定偏移量范围的数据 1.客户端测试 ...

  6. 流式数据分析模型kafka+storm

    http://www.cnblogs.com/panfeng412/archive/2012/07/29/storm-stream-model-analysis-and-discussion.html ...

  7. Java 8 集合之流式(Streams)操作, Streams API 详解

    因为当时公司的业务需要对集合进行各种各样的业务逻辑操作,为了提高性能,就用到了这个东西,因为以往我们以前用集合都是需要去遍历(串行),所以效率和性能都不是特别的好,而Streams就可以使用并行的方式 ...

  8. Spark之 Spark Streaming流式处理

    SparkStreaming Spark Streaming类似于Apache Storm,用于流式数据的处理.Spark Streaming有高吞吐量和容错能力强等特点.Spark Streamin ...

  9. Mysql中使用JDBC流式查询避免数据量过大导致OOM

    一.前言 java 中MySQL JDBC 封装了流式查询操作,通过设置几个参数,就可以避免一次返回数据过大导致 OOM. 二.如何使用 2.1 之前查询 public void selectData ...

随机推荐

  1. VSCode调试go语言出现:exec: "gcc": executable file not found in %PATH%

    1.问题描述 由于安装VS15 Preview 5,搞的系统由重新安装一次:在用vscdoe编译go语言时,出现以下问题: # odbcexec: "gcc": executabl ...

  2. (翻译)FIFO In Hardware

    翻译一些自己觉得有价值的材料,工作中碰到英语大多数是读,基本没有写或者翻的,翻得不好不到位的敬请指摘. 同时也附原文以供参考. http://electronics.stackexchange.com ...

  3. 【夯实PHP基础】nginx php-fpm 输出php错误日志

    本文地址 原文地址 分享提纲: 1.概述 2.解决办法(解决nginx下php-fpm不记录php错误日志) 1. 概述 nginx是一个web服务器,因此nginx的access日志只有对访问页面的 ...

  4. postgresql无法安装pldbgapi的问题

    要对函数进行调试需要安装插件pldbgapi,当初在windows上面的postgresql实例中执行了一下语句就安装上了: create extension pldbgapi; 但是在linux中执 ...

  5. C#高级知识点&(ABP框架理论学习高级篇)——白金版

    前言摘要 很早以前就有要写ABP高级系列教程的计划了,但是迟迟到现在这个高级理论系列才和大家见面.其实这篇博客很早就着手写了,只是楼主一直写写停停.看看下图,就知道这篇博客的生产日期了,谁知它的出厂日 ...

  6. UWP控件与DataBind

    在uwp开发中必不可少的一个环节就是各种通用的控件的开发,所以在闲暇时间汇总了一下在uwp开发中控件的几种常用写法,以及属性的几种绑定方式,有可能不全面,请大家多多包涵 :) 1.先从win10新增的 ...

  7. iOS实现UITableViewDataSource与Controller的分离

    写在前面 在之前的项目中好多处用到了tableView,然而之前不懂得将代理方法实现分离,所以每在一处用到tableView就要在controller中写一遍UITableViewDataSource ...

  8. C#设计模式-策略者模式

    状态模式是对某个对象状态的抽象,而本文要介绍的策略模式也就是对策略进行抽象,策略的意思就是方法,所以也就是对方法的抽象,下面具体分享下我对策略模式的理解. 一. 策略者(Stragety)模式 在现实 ...

  9. WCF学习之旅—WCF服务配置(十四)

    一.概述 我们在前面章节中讲了寄宿,在前面的实例中也用到了配置文件,这一篇主要讲讲如何在应用配置文件,提高WCF程序的灵活性.在编写WCF服务应用程序时,编写配置项也是其中一项主要工作,在前面的几个示 ...

  10. 8.SVM用于多分类

    从前面SVM学习中可以看出来,SVM是一种典型的两类分类器.而现实中要解决的问题,往往是多类的问题.如何由两类分类器得到多类分类器,就是一个值得研究的问题. 以文本分类为例,现成的方法有很多,其中一劳 ...