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指定。