table和API
- table和API基本示例
- 引入的依赖
- 代码基本步骤
- 代码演示
- 基本程序结构
- 表
- 表的概念
- 表的创建
- 从文件系统中创建表
- 从Kafka系统中创建表
- 表的查询
- 使用table Api
- 使用SQL
- DataStream转换成为表
- 创建临时视图
- 输出表
- 输出到文件
- 输出外部数据库
- 更新模式
- 输出到kafka
- 输出到Mysql
table和API基本示例
引入的依赖
planner计划。计划器,这是tableAPI中最主要的部分,提供一个运行时的环境。基于运行时的环境解析流式处理程序,生成一个表的执行计划,2.12是Scala的版本。1.10是flink的版本。
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-table-planner_2.12</artifactId>
<version>1.10.1</version>
</dependency>
bridge桥接器,语言的转换工具。2.12是Scala的版本。1.10是flink的版本。下面是Scala语言的转换器,如果用Java语言就用Java语言的转换器。
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-table-api-scala-bridge_2.12</artifactId>
<version>1.10.1</version>
</dependency>
代码基本步骤
1、创建流式处理环境,获取到数据,将数据转换成一定的格式的流数据。
2、创建表的处理环境,表处理环境从数据流中获取流数据,作为一张表。
3、Table的方式: 在这张表上进行做一些方法操作,得到最终想要的表。
4、sql的方式:在环境中注册这张表,然后写一个SQL作为一个字符串,然后进行执行。
需要注意的地方:
1、表环境的创建是基于流环境的创建的。流环境创建的时候,直接是流执行环境.获取执行环境。而表环境创建的时候,表执行环境.创建(流环境变量)。
2、从流中获取的数据,创建的表,并没有在表环境中注册管理,因此需要注册创建一个视图。
3、print打印的时候,表转换成流的话可以使用print这个方法。
代码演示
package com.atguigu.apitest
import org.apache.flink.streaming.api.scala._
import org.apache.flink.table.api.Table
// scala语言用这个,Java对应的改成Java
import org.apache.flink.table.api.scala._
// import org.apache.flink.table.api.java.StreamTableEnvironment
case class SensorReadingTabletest (id :String , timestamp: Long ,tempreture:Double)
object tabletest {
def main(args: Array[String]): Unit = {
// 定义流式处理环境。
val env: StreamExecutionEnvironment = StreamExecutionEnvironment.getExecutionEnvironment
// 从文件中读取数据。
val inpath = "D:\\code_put\\flink_tuturial\\src\\main\\resources\\Sensor.txt"
val inputStream: DataStream[String] = env.readTextFile(inpath)
val dataStream: DataStream[SensorReadingTabletest] = inputStream.map(data => {
val strings = data.split(",")
SensorReadingTabletest(strings(0), strings(1).toLong, strings(2).toDouble) //方法的返回值
})
//创建表的执行环境。
// 底层是流处理,基于流处理来创建当前的表环境。
// scala语言用这个,Java对应的改成Java,注意导包。
//import org.apache.flink.table.api.scala._
val tabEnv: StreamTableEnvironment = StreamTableEnvironment.create(env)
//基于流创建一张表
// --也就是流中的数据放入到表中,从流中拿来数据作为一张表
// dataTable现在是一张表,想象成SQL语句中的表。
val dataTable: Table = tabEnv.fromDataStream(dataStream)
// -- 用table实现,这种就是类比于在 SQLyoug中进行手动选择,最后展示出来什么样的效果。
// 调用table API ,通过sql之后形成的新的表。
val resultTable = dataTable
.select("id,tempreture") //相当于SQL中的select,要选那些字段。
.filter("id ='sensor_1'") //过滤条件,相当于SQL中的where。
//转换成流的话,就是可以进行print输出。
resultTable.toAppendStream[(String,Double)].print("tab")
// 如果用SQL写的话,还是有一点点的麻烦。
// 1、对dataTable进行注册,因为dataTable就是直接从流中直接转换过来的。
// 2、dataTable这张表是Table数据类型,并没有在当前的表环境中有catalog,并没有管理起来。
// 3、如果想写SQL的话,必须是在目录里面有注册的,才可以直接在SQL里面直接调用这个表名字。
tabEnv.createTemporaryView("dataTable",dataTable) //第一个是注册的视图名字,第二个是对哪个表进行注册。
// 然后在SQL中可以直接用这个视图的名字。
val sql :String ="select id,tempreture from dataTable where id = 'sensor_1'"
//基于环境,进行执行SQL。
val resultSqlTable = tabEnv.sqlQuery(sql)
//转换成流的话,就是可以进行print输出。
resultSqlTable.toAppendStream[(String, Double)].print("sql")
env.execute()
}
}
基本程序结构
这里的connect是连接外部的数据系统,文件,kafka…。读入,写出。
这里是直接从外部系统读取数据,就可以直接在表环境中注册出来,注册的是表,不是视图。如果是从流中读取数据,那么就是注册的是视图。
1、 tableApi要用的话,必须先得到table数据类型,那怎么得到Table数据类型呢?用from方法,把当前表环境注册好的表读出来,得到一个Table类型的表。
2、SQL的话,直接用环境注册好的那张表。
注意from后面加双引号。
表
表的概念
表的创建
从文件系统中创建表
connect和createTemporaryTable中间点击with,只有.withSchema()、.withFormat(),这两个方法。
代码
package com.atguigu.tableApiTests
import org.apache.flink.streaming.api.scala._
import org.apache.flink.table.api.DataTypes
import org.apache.flink.table.api.scala._
import org.apache.flink.table.descriptors.{FileSystem, OldCsv, Schema}
object tableApiTest {
def main(args: Array[String]): Unit = {
//1、创建表执行环境,前提是有流式的执行环境
val env = StreamExecutionEnvironment.getExecutionEnvironment
val tabEnv = StreamTableEnvironment.create(env)
//读取文件
tabEnv.
connect(new FileSystem().path("D:\\code_put\\flink_tuturial\\src\\main\\resources\\Sensor.txt"))
.withFormat(new OldCsv()) //因为外部文件是逗号分割的,所以是csv格式。
//这里用的旧版的,新版的需要添加一个依赖。
.withSchema(new Schema()
.field("id",DataTypes.STRING())
.field("timestamp",DataTypes.BIGINT())
.field("tempreture",DataTypes.DOUBLE())
) // 指定当前表结构每一个字段是什么。
.createTemporaryTable("table1")
val table2 = tabEnv.from("table1")
table2.toAppendStream[(String,Long,Double)].print()
env.execute()
}
}
从Kafka系统中创建表
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-csv</artifactId>
<version>1.10.1</version>
</dependency>
代码
package com.atguigu.tableApiTests
import org.apache.flink.streaming.api.scala._
import org.apache.flink.table.api.DataTypes
import org.apache.flink.table.api.scala._
import org.apache.flink.table.descriptors.{Csv, FileSystem, Kafka, OldCsv, Schema}
object tableApiTest {
def main(args: Array[String]): Unit = {
//1、创建表执行环境,前提是有流式的执行环境
val env = StreamExecutionEnvironment.getExecutionEnvironment
val tabEnv = StreamTableEnvironment.create(env)
tabEnv.
connect( new Kafka()
.version("0.11") //kafka版本
.topic("sensor") //主题
.property("zookeeper.connect","hadoop102:2181") // zookpeer的端口号: 2181
.property("bootstrap.servers","hadoop102:9092") // kafka的端口号
)
.withFormat(new Csv()) //文本格式的文件
.withSchema(new Schema()
.field("id",DataTypes.STRING())
.field("timestamp",DataTypes.BIGINT())
.field("tempreture",DataTypes.DOUBLE())
) // 指定当前表结构每一个字段是什么。
.createTemporaryTable("table1")
val table2 = tabEnv.from("table1")
table2.toAppendStream[(String,Long,Double)].print()
env.execute()
}
}
表的查询
使用table Api
使用SQL
DataStream转换成为表
基于名称的这种,可以和流中的字段的顺序不一致。
创建临时视图
输出表
连接到外部系统,然后在环境中注册一张表。
输出到文件
输出到打印台上的测试代码:
package com.atguigu.tableApiTests
import org.apache.flink.streaming.api.scala._
import org.apache.flink.table.api.{DataTypes, Table}
import org.apache.flink.table.api.scala._
import org.apache.flink.table.descriptors.{FileSystem, OldCsv, Schema}
object fileOutPutTest {
def main(args: Array[String]): Unit = {
//1、创建表执行环境,前提是有流式的执行环境
val env = StreamExecutionEnvironment.getExecutionEnvironment
env.setParallelism(1)
val tabEnv = StreamTableEnvironment.create(env)
//2、读取数据
//读取文件
tabEnv.
connect(new FileSystem().path("D:\\code_put\\flink_tuturial\\src\\main\\resources\\Sensor.txt"))
.withFormat(new OldCsv()) //因为外部文件是逗号分割的,所以是csv格式。
.withSchema(new Schema()
.field("id",DataTypes.STRING())
.field("timestamp",DataTypes.BIGINT())
.field("temperature",DataTypes.DOUBLE())
) // 指定当前表结构每一个字段是什么。
.createTemporaryTable("table1")
//3、转换操作
val table2 = tabEnv.from("table1")
//简单转换
val resultTable:Table = table2
.select('id,'temperature) //用半个单引号',引起来字段。
.filter('id==="sensor_1")// 用===进行筛选。
//聚合转换
val aggTable = resultTable
.groupBy('id)
.select('id, 'id .count as 'count)
//聚合转换的话,不能有重复数据,所以之前在表中存在的,先撤回掉,更新后的放进去。
aggTable.toRetractStream[(String,Long)].print()
env.execute()
}
}
文本中有3条sensor_1,代码运行的结果:
true表示数据进入表中,false表示数据从表中撤回。因为我们没有办法改变已经输出的数据,因此前面加一个true或者false进行判断,是不是最新的数据。
输出到文件的代码:
文件系统-----只能追加,不能有撤回,更改的操作,也就是不支持聚合操作。写出到文件的数据,不可能撤回掉。
package com.atguigu.tableApiTests
import org.apache.flink.streaming.api.scala._
import org.apache.flink.table.api.{DataTypes, Table}
import org.apache.flink.table.api.scala._
import org.apache.flink.table.descriptors.{FileSystem, OldCsv, Schema}
object fileOutPutTest {
def main(args: Array[String]): Unit = {
//1、创建表执行环境,前提是有流式的执行环境
val env = StreamExecutionEnvironment.getExecutionEnvironment
env.setParallelism(1)
val tabEnv = StreamTableEnvironment.create(env)
//2、读取数据
//读取文件
tabEnv.
connect(new FileSystem().path("D:\\code_put\\flink_tuturial\\src\\main\\resources\\Sensor.txt"))
.withFormat(new OldCsv()) //因为外部文件是逗号分割的,所以是csv格式。
.withSchema(new Schema()
.field("id",DataTypes.STRING())
.field("timestamp",DataTypes.BIGINT())
.field("temperature",DataTypes.DOUBLE())
) // 指定当前表结构每一个字段是什么。
.createTemporaryTable("table1")
//3、转换操作
val table2 = tabEnv.from("table1")
//简单转换
val resultTable:Table = table2
.select('id,'temperature)
.filter('id==="sensor_1")
//输出到文件
tabEnv.
connect(new FileSystem().path("D:\\code_put\\flink_tuturial\\src\\main\\resources\\SensorOut1.txt"))
.withFormat(new OldCsv()) //因为外部文件是逗号分割的,所以是csv格式。
.withSchema(new Schema()
.field("id",DataTypes.STRING())
.field("temperature",DataTypes.DOUBLE())
) // 指定当前表结构每一个字段是什么。
.createTemporaryTable("outputTable")
// 输出到文件
resultTable.insertInto("outputTable")
env.execute()
}
}
输出外部数据库
更新操作的表还要写到外部系统,那么就是写入到一些数据库里面,这些数据库支持实现其他的tablesink。
更新模式
1、追加模式:只追加。
2、撤回模式:增(新增一条)、删(删除一条)、改(撤回一条,发出一条)。
3、upsert模式:(key,value)这种,插入的时候,没有发现某个key,那么插入该数据。更新的时候找到key,然后更新对应的value。
输出到kafka
//输入还是输出,并不是由连接器决定,而是由转换操作决定。
tabEnv.
connect( new Kafka()
.version("0.11") //kafka版本
.topic("sensor") //主题
.property("zookeeper.connect","hadoop102:2181") // zookpeer的端口号: 2181
.property("bootstrap.servers","hadoop102:9092") // kafka的端口号
)
.withFormat(new Csv()) //文本格式的文件
.withSchema(new Schema()
.field("id",DataTypes.STRING())
.field("timestamp",DataTypes.BIGINT())
.field("tempreture",DataTypes.DOUBLE())
) // 指定当前表结构每一个字段是什么。
.createTemporaryTable("kafka_table")
resultTable.insertInto("kafka_table")
代码
package com.atguigu.tableApiTests
import org.apache.flink.streaming.api.scala._
import org.apache.flink.table.api.DataTypes
import org.apache.flink.table.api.scala._
import org.apache.flink.table.descriptors.{Csv, Kafka, Schema}
object KafkaPipeline {
def main(args: Array[String]): Unit = {
//1、创建表执行环境,前提是有流式的执行环境
val env = StreamExecutionEnvironment.getExecutionEnvironment
val tabEnv = StreamTableEnvironment.create(env)
//2、从kafka中读取数据。
tabEnv.
connect( new Kafka()
.version("0.11") //kafka版本
.topic("sensor") //主题
.property("zookeeper.connect","hadoop102:2181") // zookpeer的端口号: 2181
.property("bootstrap.servers","hadoop102:9092") // kafka的端口号
)
.withFormat(new Csv()) //文本格式的文件
.withSchema(new Schema()
.field("id",DataTypes.STRING())
.field("timestamp",DataTypes.BIGINT())
.field("temperature",DataTypes.DOUBLE())
) // 指定当前表结构每一个字段是什么。
.createTemporaryTable("kafka_source")
//3、转换操作
val sensorTable = tabEnv.from("kafka_source")
//简单转换
val resultTable = sensorTable
.select('id, 'temperature)
.filter('id === "sensor_1")
//4、输出
//source和sink的区别就是: topic和临时表的名字不能一样,其他的地方不用改。
tabEnv.
connect( new Kafka()
.version("0.11") //kafka版本
.topic("sensor_out") //主题
.property("zookeeper.connect","hadoop102:2181") // zookpeer的端口号: 2181
.property("bootstrap.servers","hadoop102:9092") // kafka的端口号
)
.withFormat(new Csv()) //文本格式的文件
.withSchema(new Schema()
.field("id",DataTypes.STRING())
.field("temperature",DataTypes.DOUBLE())
) // 指定当前表结构每一个字段是什么。
.createTemporaryTable("kafka_sink")
//将 resultTable表 插入到外部表 kafka_sink 中。
resultTable.insertInto("kafka_sink")
env.execute()
}
}
启动生产者的kafka,然后启动消费者的kafka,启动代码。在生产者中生产数据,然后在消费者中可以看到数据。
输出到Mysql
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-jdbc_2.12</artifactId>
<version>1.10.1</version>
</dependency>
并不是connect的方式,而是DDL的方式。
/ 输出到 Mysql
val sinkDDL: String =
"""
|create table jdbcOutputTable (
| id varchar(20) not null,
| cnt bigint not null
|) with (
| 'connector.type' = 'jdbc',
| 'connector.url' = 'jdbc:mysql://hadoop102:3306/test',
| 'connector.table' = 'sensor_count',
| 'connector.driver' = 'com.mysql.jdbc.Driver',
| 'connector.username' = 'root',
| 'connector.password' = '123456'
|) """.stripMargin
tableEnv.sqlUpdate(sinkDDL) aggResultSqlTable.insertInto("jdbcOutputTable")
其中:
'connector.url' = 'jdbc:mysql://hadoop102:3306/test' ,表保存在MySQL的这个库中。
'connector.table' = 'sensor_count',这张表是真正在MySQL中保存的表,也就是最后写入到MySQL中,就是这张表。
jdbcOutputTable 是在表执行环境中创建的表。