Flink CDC系列之:Kafka表结构信息管理类TableSchemaInfo

这是一个 表结构信息管理类,负责维护表结构信息、字段获取器和序列化器,并提供数据转换功能。

类概述

java 复制代码
public class TableSchemaInfo

这个类封装了表的结构信息、序列化器和数据转换逻辑,是 Kafka JSON/CSV 序列化器的核心支撑组件。

核心属性

java 复制代码
private final TableId tableId;                           // 表标识
private final Schema schema;                             // 表结构定义
private final List<Integer> primaryKeyColumnIndexes;     // 主键列索引列表
private final List<RecordData.FieldGetter> fieldGetters; // 字段获取器列表
private final SerializationSchema<RowData> serializationSchema; // 序列化器

构造函数

java 复制代码
public TableSchemaInfo(
        TableId tableId,
        Schema schema,
        SerializationSchema<RowData> serializationSchema,
        ZoneId zoneId) {
    this.tableId = tableId;
    this.schema = schema;
    this.serializationSchema = serializationSchema;
    this.fieldGetters = createFieldGetters(schema, zoneId);  // 创建字段获取器
    primaryKeyColumnIndexes = new ArrayList<>();
    
    // 构建主键列索引映射
    for (int keyIndex = 0; keyIndex < schema.primaryKeys().size(); keyIndex++) {
        for (int columnIndex = 0; columnIndex < schema.getColumnCount(); columnIndex++) {
            if (schema.getColumns()
                    .get(columnIndex)
                    .getName()
                    .equals(schema.primaryKeys().get(keyIndex))) {
                primaryKeyColumnIndexes.add(columnIndex);
                break;
            }
        }
    }
}

主键索引构建逻辑:

  • 遍历所有主键名称
  • 在列表中查找对应的列索引
  • 建立主键名称到列索引的映射

核心方法详解
getRowDataFromRecordData() - 数据转换方法

java 复制代码
public RowData getRowDataFromRecordData(RecordData recordData, boolean primaryKeyOnly) {
    if (primaryKeyOnly) {
        return createPrimaryKeyRowData(recordData);   // 只包含主键
    } else {
        return createFullRowData(recordData);         // 包含所有字段
    }
}

主键模式数据转换

java 复制代码
private RowData createPrimaryKeyRowData(RecordData recordData) {
    // 创建包含表名 + 主键字段的RowData
    GenericRowData genericRowData = new GenericRowData(primaryKeyColumnIndexes.size() + 1);
    
    // 第一个字段:表标识
    genericRowData.setField(0, StringData.fromString(tableId.toString()));
    
    // 后续字段:所有主键值
    for (int i = 0; i < primaryKeyColumnIndexes.size(); i++) {
        genericRowData.setField(
                i + 1,
                fieldGetters
                        .get(primaryKeyColumnIndexes.get(i))
                        .getFieldOrNull(recordData));
    }
    return genericRowData;
}

输出结构:

bash 复制代码
RowData[TableId, PK1, PK2, ...]

完整数据模式转换

java 复制代码
private RowData createFullRowData(RecordData recordData) {
    // 创建包含所有字段的RowData
    GenericRowData genericRowData = new GenericRowData(recordData.getArity());
    
    // 设置所有字段值
    for (int i = 0; i < recordData.getArity(); i++) {
        genericRowData.setField(i, fieldGetters.get(i).getFieldOrNull(recordData));
    }
    return genericRowData;
}
bash 复制代码
方法目的:这个方法的作用是从 RecordData 创建一个包含所有字段值的完整 RowData 对象。

关键组件:
GenericRowData:Apache Flink中的一个类,表示通用的行数据结构
recordData.getArity():获取输入数据的字段总数
fieldGetters:一个列表或数组,包含了从 RecordData 中提取各个字段值的方法引用

工作流程:
首先根据字段数初始化一个新的 GenericRowData
然后通过循环逐个字段地从原始 recordData 中读取值
每个字段都通过对应的 fieldGetter 安全地获取值(可能为空)
最后返回构建完成的 RowData
这种方法常用于数据处理管道中,当需要在不同数据格式之间进行转换时,确保所有字段都被正确复制和保留。

输出结构:

bash 复制代码
RowData[Col1, Col2, Col3, ...]

createFieldGetters() - 创建字段获取器

java 复制代码
private static List<RecordData.FieldGetter> createFieldGetters(Schema schema, ZoneId zoneId) {
    List<RecordData.FieldGetter> fieldGetters = new ArrayList<>(schema.getColumns().size());
    for (int i = 0; i < schema.getColumns().size(); i++) {
        fieldGetters.add(createFieldGetter(schema.getColumns().get(i).getType(), i, zoneId));
    }
    return fieldGetters;
}

作用:为每个列创建对应的字段值获取器,支持类型安全的数据访问。

createFieldGetter() - 创建类型特定的字段获取器

这是最核心的方法,为每种数据类型创建专门的获取器:

字符串类型

java 复制代码
case CHAR:
case VARCHAR:
    fieldGetter =
            record ->
                    BinaryStringData.fromString(record.getString(fieldPos).toString());

数值类型

java 复制代码
case BOOLEAN: fieldGetter = record -> record.getBoolean(fieldPos);
case TINYINT: fieldGetter = record -> record.getByte(fieldPos);
case SMALLINT: fieldGetter = record -> record.getShort(fieldPos);
case INTEGER: fieldGetter = record -> record.getInt(fieldPos);
case BIGINT: fieldGetter = record -> record.getLong(fieldPos);
case FLOAT: fieldGetter = record -> record.getFloat(fieldPos);
case DOUBLE: fieldGetter = record -> record.getDouble(fieldPos);

高精度数值

java 复制代码
case DECIMAL:
    final int decimalPrecision = getPrecision(fieldType);
    final int decimalScale = getScale(fieldType);
    fieldGetter =
            record ->
                    DecimalData.fromBigDecimal(
                            record.getDecimal(fieldPos, decimalPrecision, decimalScale)
                                    .toBigDecimal(),
                            decimalPrecision,
                            decimalScale);

日期时间类型

java 复制代码
case DATE:
    fieldGetter = record -> (int) record.getDate(fieldPos).toEpochDay();  // 转为天数

case TIME_WITHOUT_TIME_ZONE:
    fieldGetter = record -> (int) record.getTime(fieldPos).toMillisOfDay();  // 转为毫秒数

case TIMESTAMP_WITHOUT_TIME_ZONE:
    fieldGetter =
            record ->
                    TimestampData.fromTimestamp(
                            record.getTimestamp(fieldPos, getPrecision(fieldType))
                                    .toTimestamp());

case TIMESTAMP_WITH_TIME_ZONE:
case TIMESTAMP_WITH_LOCAL_TIME_ZONE:
    fieldGetter =
            record ->
                    TimestampData.fromInstant(
                            record.getLocalZonedTimestampData(
                                            fieldPos,
                                            DataTypeChecks.getPrecision(fieldType))
                                    .toInstant());

二进制类型

java 复制代码
case BINARY:
case VARBINARY:
    fieldGetter = record -> record.getBinary(fieldPos);

空值安全包装

java 复制代码
if (!fieldType.isNullable()) {
    return fieldGetter;  // 非空字段直接返回
}
// 可空字段添加空值检查
return row -> {
    if (row.isNullAt(fieldPos)) {
        return null;
    }
    return fieldGetter.getFieldOrNull(row);
};

设计模式:访问者模式 + 策略模式

访问者模式应用

java 复制代码
// FieldGetter 作为访问者,访问 RecordData 的不同字段
interface FieldGetter {
    Object getFieldOrNull(RecordData record);
}

// 为每种数据类型创建具体的访问策略
FieldGetter stringGetter = record -> ...;
FieldGetter intGetter = record -> ...;
FieldGetter timestampGetter = record -> ...;

策略模式应用

java 复制代码
// 根据数据类型选择不同的获取策略
switch (fieldType.getTypeRoot()) {
    case CHAR: return stringGetter;
    case INTEGER: return intGetter;
    case TIMESTAMP: return timestampGetter;
    // ...
}

实际使用示例

在 JSON 序列化器中的使用

java 复制代码
public class JsonSerializationSchema {
    private final Map<TableId, TableSchemaInfo> jsonSerializers;
    
    public byte[] serialize(Event event) {
        DataChangeEvent dataChangeEvent = (DataChangeEvent) event;
        RecordData recordData = getRecordData(dataChangeEvent);
        
        TableSchemaInfo tableSchemaInfo = jsonSerializers.get(dataChangeEvent.tableId());
        
        // 转换为只包含主键的RowData,然后序列化为JSON
        RowData rowData = tableSchemaInfo.getRowDataFromRecordData(recordData, true);
        return tableSchemaInfo.getSerializationSchema().serialize(rowData);
    }
}

数据转换流程

java 复制代码
RecordData (Flink CDC内部格式)
    ↓ TableSchemaInfo.getRowDataFromRecordData()
RowData (Flink Table内部格式)  
    ↓ SerializationSchema.serialize()
byte[] (JSON/CSV格式)

总结

TableSchemaInfo 类是一个高效的表结构管理和数据转换组件:

✅ 类型安全转换:为每种数据类型提供专门的获取器

✅ 性能优化:预计算字段获取器和主键索引

✅ 空值安全:自动处理可空字段的空值检查

✅ 灵活的输出模式:支持主键模式和全字段模式

✅ 易于扩展:支持新数据类型和输出模式

✅ 统一的接口:封装复杂的类型转换逻辑

它是 Flink CDC 到 Kafka 序列化过程中的核心桥梁,将 Flink CDC 的内部数据格式转换为序列化器所需的格式,确保了数据转换的类型安全和高效性能。

相关推荐
最笨的羊羊7 小时前
Flink CDC系列之: Kafka 数据接收器实现类KafkaDataSink
kafka·flink cdc系列·数据接收器实现类·kafkadatasink
最笨的羊羊2 天前
Flink CDC系列之:Doris 模式工具类DorisSchemaUtils
doris·flink cdc系列·schemautils·模式工具类
最笨的羊羊2 天前
Flink CDC系列之:flink-cdc-base模块config
flink cdc系列·config·flink-cdc-base
最笨的羊羊2 天前
Flink CDC系列之:flink-cdc-base模块dialect
flink cdc系列·flink-cdc-base·dialect
最笨的羊羊3 天前
Flink CDC系列之:JSON 序列化器JsonRowDataSerializationSchemaUtils
json·flink cdc系列·serialization·json 序列化器·rowdata·schemautils
最笨的羊羊3 天前
Flink CDC系列之:Kafka CSV 序列化器CsvSerializationSchema
kafka·csv·schema·flink cdc系列·serialization·序列化器
最笨的羊羊3 天前
Flink CDC系列之:Kafka的Debezium JSON 结构定义类DebeziumJsonStruct
kafka·debezium·flink cdc系列·debezium json·结构定义类·jsonstruct
最笨的羊羊3 天前
Flink CDC系列之:Kafka JSON 序列化类型枚举
flink cdc系列·kafka json·序列化类型枚举
最笨的羊羊4 天前
Flink CDC系列之:数据接收器工厂类DorisDataSinkFactory
doris·flink cdc系列·数据接收器工厂类·datasinkfactory