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")