1、将 DataStream 转换成表
//创建一个流式的执行环境
val env: StreamExecutionEnvironment = StreamExecutionEnvironment.getExecutionEnvironment
// 1、 基于dataStream 先流式的读取数据源
val inputStream: DataStream[String] = env.readTextFile("sensor.txt")
// 2、map成样例类,这样就不用定义schema了
val ds: DataStream[_02_SensorReading] = inputStream.map(data => {
val arr: Array[String] = data.split(",")
_02_SensorReading(arr(0), arr(1).toLong, arr(2).toDouble)
})
// 创建表的执行环境
val settings = EnvironmentSettings.newInstance()
.useOldPlanner() // 老版本的连接器
.inStreamingMode()
.build()
val tableEnv: StreamTableEnvironment = StreamTableEnvironment.create(env, settings)
// 3、将dataStream 转换为Table
val sensorTable: Table = tableEnv.fromDataStream(ds)
/**
* 将 DataStream 转换成表
*
* DataStream 中的数据类型,与表的 Schema 之间的对应关系, 是按
* 照样例类中的字段名来对应的(name-based mapping),所以还可以用 as 做重命名。
*/
val sensorTable2: Table = tableEnv.fromDataStream(ds, 'timestamp as 'myTimeStamp, 'id as 'myId, 'temperature)
// 基于位置的对应
val sensorTable3: Table = tableEnv.fromDataStream(ds, 'myId, 'ts)
2、将表转换成 DataStream
/**
* 将表转换成 DataStream
*
* 将表转换为 DataStream 或 DataSet 时,需要指定生成的数据类型,即要将表的每一行转换成的数据类型。
* 通常,最方便的转换类型就是 Row。当然,因为结果的所有字段类型都是明确的,我们也经常会用元组类型来表示。
*
* 表作为流式查询的结果,是动态更新的。 所以, 将这种动态查询转换成的数据流,同样需要对表的更新操作进行编码,进而有不同的转换模式。
* Table API 中表到 DataStream 有两种模式:
* ⚫ 追加模式(Append Mode)
* 用于表只会被插入(Insert)操作更改的场景。
* ⚫ 撤回模式(Retract Mode)
* 用于任何场景。 有些类似于更新模式中 Retract 模式,它只有 Insert 和 Delete 两类操作。
* 得到的数据会增加一个 Boolean 类型的标识位(返回的第一个字段),用它来表示到底
* 是新增的数据(Insert),还是被删除的数据(老数据, Delete)。
*/
val resultStream: DataStream[Row] = tableEnv.toAppendStream[Row](sensorTable)
val aggResultStream: DataStream[(Boolean, (String, Long))] =
tableEnv.toRetractStream[(String, Long)](sensorTable)
resultStream.print("result")
aggResultStream.print("aggResult")
// 所以,没有经过 groupby 之类聚合操作,可以直接用 toAppendStream 来转换;而如果经过了聚合,有更新操作,一般就必须用 toRetractDstream。
3、创建临时视图
/**
* 创建临时视图
*
* 事实上,在 Table API 中,可以认为 View 和 Table 是等价的。
*
* 创建临时视图的第一种方式,就是直接从 DataStream 转换而来。同样,可以直接对应
* 字段转换;也可以在转换的时候,指定相应的字段。
*/
tableEnv.createTemporaryView("sensorView01",ds)
tableEnv.createTemporaryView("sensorView02",ds,'id ,'temperature,'timestamp as 'ts)
// 另外还能基于Table创建视图
tableEnv.createTemporaryView("sensorView03",sensorTable)
4、表的输出
(1)输出到外部的系统
/**
* 输出表
*
* 表的输出,是通过将数据写入 TableSink 来实现的。 TableSink 是一个通用接口,可以支持不同的文件格式、存储数据库和消息队列。
* 具体实现, 输出表最直接的方法,就是通过 Table.insertInto() 方法将一个 Table 写入注册过的 TableSink 中。
*/
// 1、输出到外部的系统
tableEnv.connect(
new FileSystem().path("")
).withFormat(new Csv()) // 定义格式化方法 Csv格式
.withSchema(
new Schema() // 定义表结构
.field("id",DataTypes.STRING())
.field("temp",DataTypes.DOUBLE())
).createTemporaryTable("outputTable") // 创建一个临时表
sensorTable.insertInto("outputTable")
(2)输出到kafka
tableEnv.connect(
new Kafka()
.version("0.11")
.topic("sinkTest")
.property("zookeeper.connect", "localhost:2181")
.property("bootstrap.servers", "localhost:9092")
)
.withFormat( new Csv() )
.withSchema( new Schema()
.field("id", DataTypes.STRING())
.field("temp", DataTypes.DOUBLE())
)
.createTemporaryTable("kafkaOutputTable")
sensorTable.insertInto("kafkaOutputTable")
(3)输出到 ElasticSearch
// 3、输出到 ElasticSearch
/*
ElasticSearch 的 connector 可以在 upsert(update+insert,更新插入) 模式下操作,这样
就可以使用 Query 定义的键(key) 与外部系统交换 UPSERT/DELETE 消息。
另外, 对于“仅追加”(append-only)的查询, connector 还可以在 append 模式下操作,
这样就可以与外部系统只交换 insert 消息
*/
// 输出到 es
tableEnv.connect(
new Elasticsearch()
.version("6")
.host("localhost", 9200, "http")
.index("sensor")
.documentType("temp")
)
.inUpsertMode() // 指定是 Upsert 模式
.withFormat(new Json()) // es目前支持的数据格式,只有 Json,而 flink 本身并没有对应的支持,所以还需要引入依赖
.withSchema( new Schema()
.field("id", DataTypes.STRING())
.field("count", DataTypes.BIGINT())
)
.createTemporaryTable("esOutputTable")
sensorTable.insertInto("esOutputTable")
(4)输出到 MySql
// 4、输出到 MySql
/*
jdbc 连接的代码实现比较特殊,因为没有对应的 java/scala 类实现 ConnectorDescriptor,
所以不能直接tableEnv.connect()。不过Flink SQL留下了执行DDL的接口: tableEnv.sqlUpdate()。
对于 jdbc 的创建表操作,天生就适合直接写 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://localhost:3306/test',
| 'connector.table' = 'sensor_count',
| 'connector.driver' = 'com.mysql.jdbc.Driver',
| 'connector.username' = 'root',
| 'connector.password' = '123456'
|)
""".stripMargin
tableEnv.sqlUpdate(sinkDDL)
sensorTable.insertInto("jdbcOutputTable")