DataStream<Row>如何注册为flink table (An input of GenericTypeInfo<Row> cannot be converted to Table)

1. 问题

当使用Flink中的registerDataStream注册table时,一直报错:

org.apache.flink.table.api.TableException: An input of GenericTypeInfo<Row> cannot be converted to Table. Please specify the type of the input with a RowTypeInfo.

查看源码发现,当DataStream的数据类型为Row时,注册table,调用getFieldInfo去获取流数据的类型信息失败,因此无法指定类型。
在这里插入图片描述
于是开始各种寻找解决办法,无果。当看到大佬们在官网的讨论之后,彻底死心,只能另辟蹊径。
https://issues.apache.org/jira/browse/FLINK-6476

2. 解决办法

2.1. 创建数据流时指定TypeInfomation

查看源码发现,StreamExecutionEnvironment提供了一些方法在创建DataStream时可以指定TypeInfo(常用的addSource方法也可以指定TypeInfo),下面列出部分:
在这里插入图片描述
以fromCollection从本地集合读取数据源为例进行说明:

import org.apache.flink.api.common.typeinfo.BasicTypeInfo;
import org.apache.flink.api.common.typeinfo.TypeInformation;
import org.apache.flink.api.java.typeutils.RowTypeInfo;
import org.apache.flink.streaming.api.datastream.DataStream;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.table.api.Table;
import org.apache.flink.table.api.java.StreamTableEnvironment;
import org.apache.flink.types.Row;
import java.util.ArrayList;

/**
 * Created by search-lemon on 2020/3/6.
 */
public class TestRow {
    public static void main(String[] args) throws Exception {
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        StreamTableEnvironment tableEnv = StreamTableEnvironment.getTableEnvironment(env);

        ArrayList<Row> rowCollection = new ArrayList<>();
        rowCollection.add(Row.of("3", 3, 7));
        rowCollection.add(Row.of("5", 2, 6));

        // 根据所造数据,自己生成TypeInformation
        RowTypeInfo rowTypeInfo = getRowTypeInfo();
        DataStream<Row> ds = env.fromCollection(rowCollection, rowTypeInfo);
        tableEnv.registerDataStream("Mytable", ds);

        String sql = "select a, c from Mytable";
        Table result = tableEnv.sqlQuery(sql);

        // 将table转换成DataStream
        DataStream<Row> resultData = tableEnv.toAppendStream(result, Row.class);

        resultData.print();
        env.execute();
    }

    /**
     * 生成Row类型的TypeInformation.
     */
    private static RowTypeInfo getRowTypeInfo() {
        TypeInformation[] types = new TypeInformation[3]; // 3个字段
        String[] fieldNames = new String[3];

        types[0] = BasicTypeInfo.STRING_TYPE_INFO;
        types[1] = BasicTypeInfo.INT_TYPE_INFO;
        types[2] = BasicTypeInfo.INT_TYPE_INFO;

        fieldNames[0] = "a";
        fieldNames[1] = "b";
        fieldNames[2] = "c";

        return new RowTypeInfo(types, fieldNames);
    }
}

运行结果:

3,7
5,6

上例中,使用了env.fromCollection() 生成DataStream,当然其它生成DataStream的方法,如常用的env.addSource()也有可以指定TypeInfomation的实现,当数据类型为Row时,就几乎必须指定TypeInfomation。其它的有些非Row类型,源码中实现了可推断出TypeInfomation,这时可以不用指定。

特殊情况:另外,当使用env.fromCollection(Collection data)方法从基本集合中读取数据生成DataStream时,也不用特意指定TypeInfomation,因为源码中会根据集合数据进行类型推断。此时使用方式如下:

DataStream<Row> ds = env.fromCollection(rowCollection); // 会根据集合推断字段类型
tableEnv.registerDataStream("Mytable", ds, "a,b,c"); //在这指定字段名列表

2.2. 数据流转换时指定TypeInfomation

Flink常用于实时读取数据类型为String的Kafka数据,此时在创建数据流时指定的TypeInfo对于注册table并无实际用处。因此需要在数据流进行转换拆分时,对TypeInfomation进行指定。然而DataStream常用的转换方法:如map, filter, flatMap等并不能指定TypeInfomation。

查看源码发现,DataStream提供了两个可以指定TypeInfomation的方法,分别为transform和process,并且其常用转换方法均回调的transform方法。此时可以根据源码中回调transform的方式来指定TypeInfomation。这里以FlatMap为例进行说明。

源码中flatMap调用transform:
在这里插入图片描述
自定义指定TypeInfomation的flatMap

// UdfFlatMapFunction类继承RichFlatMapFunction<String, Row>.
UdfFlatMapFunction flatMapFunction = new UdfFlatMapFunction();

// rowTypeInfo为转换后dataStream的TypeInfomation,需要自己生成. 
DataStream<Row> dataStream = sourceStream.transform("Flat Map", rowTypeInfo,new StreamFlatMap<String, Row>(UdfFlatMapFunction));

String[] fieldNames = sourceRowTypeInfo.getFieldNames();
StringBuilder sb = new StringBuilder();
for (int i = 0; i < fieldNames.length; i++) {
	if (i > 0) {
		sb.append(",");
	 }
	sb.append(fieldNames[i]);
 }

tableEnv.registerDataStream("parseTable", dataStream, sb.toString());

3. 关于TypeInfomation

TypeInfomation为Flink类型的抽象基类,如本文中使用的RowTypeInfo即为TypeInfomation的多层继承类。

DataStream<Row>常用于与flink table相互转换,因此RowTypeInfo会被经常使用,下面举例说明如何生成RowTypeInfo:

    /**
     * 根据字段信息获取getRowTypeInfo.
     * FieldInfo类中包含:
     * 字段名称:field,
     * 字段类型:dataType(这里的dataType为TypeInfomation类型。此例中使用BasicTypeInfo的flink基本数据类型)
     */
    private RowTypeInfo getRowTypeInfo(List<FieldInfo> fieldInfos) {
        TypeUtils typeUtils = new TypeUtils();
        int fieldSize = fieldInfos.size();

        TypeInformation[] types = new TypeInformation[fieldSize];
        String[] fieldNames = new String[fieldSize];

        for (int i = 0; i < fieldSize; i++) {
            types[i] = fieldInfos.get(i).getDataType();
            fieldNames[i] = fieldInfos.get(i).getField();
        }

        return new RowTypeInfo(types, fieldNames);
    }

4. 总结

当DataStream的数据类型为Row(DataStream<Row>)时,直接转换为flink table会出错,因为Row是复合类型,无法确定里面都包含哪些基本数据类型,所以需要显式声明数据类型TypeInformation。

而Flink的常用方法如map(), flatMap()等都没有指定TypeInformation这个参数。因此找到了两种可以指定TypeInformation的方式:

1⃣️ 创建数据流的时候指定;
2⃣️ 转换的时候使用底层方法transform指定。

评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值