kettle跨库迁移【主键丢失问题-源码分析】


                            kettle跨库迁移【主键丢失问题-源码分析】



此片文章将深入kettle源码分析,实现目前迁库需要的功能,本迁库方案参考其他文章(kettle的一套流程完成对整个数据库的迁移)的同时,也分别用kettle-4.2、kettle-6.1工具实现过,也搭建kettle-6.1源码调试,修改编译后实现过。现在把其中遇到的问题,将结合源码分析的形式进行总结。


数据库迁移过程需要介质存放中间数据,Kettle有两种方案,一种是文件资源库,另一种是数据库资源库,如果迁移或抽取的数据量过大,建议采用数据库资源库。


迁库需求:SQLServer-->MySql

问题来源:在迁库过程中,表结构创建造成主键丢失问题

通过源码调试分析:kettle支持的主键字段类型是有限的,在主键为nvarchar的时候程序不设置主键。


  流程介绍


因为其他文章已经有了介绍,下面就把大概的流程图附上。

整套流程分为:2个job,4个trans。

使用到的Trans插件:表输入、字段选择、复制记录到结果、从结果获取记录、设置变量、自定义java脚本、表输出。


主job为:


子job为:


要迁移的源库表名称获取,并设置到结果集,为下面的job使用


配置子job为前面的每一条记录(即每个表)执行一次该子job




获取记录中的表名称,并设置为到变量


                                   


读取当前表的结果信息,并在目标库中创建表(在个是难点)             

                                 


从来源库抽取数据到目标库

                            



主键丢失问题-源码分析


创建表结构源码定位:org.pentaho.di.core.database.Database.getDDL(String, RowMetaInterface),该函数在java自定义脚本中被调(自定义脚本略,此处重点分析主键丢失问题)。
getDDL()函数代码如下:

// 自定义脚本调用getDDL()方法
  public String getDDL( String tableName, RowMetaInterface fields, String tk, boolean use_autoinc, String pk,
                        boolean semicolon ) throws KettleDatabaseException {
    String retval;

    // First, check for reserved SQL in the input row r...
    databaseMeta.quoteReservedWords( fields );
    String quotedTk = tk != null ? databaseMeta.quoteField( tk ) : null;

    if ( checkTableExists( tableName ) ) {
      retval = getAlterTableStatement( tableName, fields, quotedTk, use_autoinc, pk, semicolon );
    } else {
      retval = getCreateTableStatement( tableName, fields, quotedTk, use_autoinc, pk, semicolon );
    }

    return retval;
  }

注意:第一次创建表走的getCreateTableStatement( ),代码如下:
代码定位:org.pentaho.di.core.database.Database.getCreateTableStatement()

/**
   * Generates SQL
   *
   * @param tableName   the table name or schema/table combination: this needs to be quoted properly in advance.
   * @param fields      the fields
   * @param tk          the name of the technical key field
   * @param use_autoinc true if we need to use auto-increment fields for a primary key
   * @param pk          the name of the primary/technical key field
   * @param semicolon   append semicolon to the statement
   * @return the SQL needed to create the specified table and fields.
   */
  public String getCreateTableStatement( String tableName, RowMetaInterface fields, String tk,
                                         boolean use_autoinc, String pk, boolean semicolon ) {
    StringBuilder retval = new StringBuilder();
    DatabaseInterface databaseInterface = databaseMeta.getDatabaseInterface();
    retval.append( databaseInterface.getCreateTableStatement() );

    retval.append( tableName + Const.CR );
    retval.append( "(" ).append( Const.CR );
    
    boolean isSetPk = false;
    
    for ( int i = 0; i < fields.size(); i++ ) {
      if ( i > 0 ) {
        retval.append( ", " );
      } else {
        retval.append( "  " );
      }

      ValueMetaInterface v = fields.getValueMeta( i );
      retval.append( databaseMeta.getFieldDefinition( v, tk, pk, use_autoinc ) );
    }    
    // At the end, before the closing of the statement, we might need to add
    // some constraints...
    // Technical keys
    if ( tk != null ) {
      if ( databaseMeta.requiresCreateTablePrimaryKeyAppend() ) {
        retval.append( ", PRIMARY KEY (" ).append( tk ).append( ")" ).append( Const.CR );
      }
    }

    // Primary keys
    if ( pk != null ) {
      if ( databaseMeta.requiresCreateTablePrimaryKeyAppend() ) {
        retval.append( ", PRIMARY KEY (" ).append( pk ).append( ")" ).append( Const.CR );
      }
    }
    retval.append( ")" ).append( Const.CR );

    retval.append( databaseMeta.getDatabaseInterface().getDataTablespaceDDL( variables, databaseMeta ) );

    if ( pk == null && tk == null && databaseMeta.getDatabaseInterface() instanceof NeoviewDatabaseMeta ) {
      retval.append( "NO PARTITION" ); // use this as a default when no pk/tk is
      // there, otherwise you get an error
    }

    if ( semicolon ) {
      retval.append( ";" );
    }

    return retval.toString();
  }

在构造每个Field时的函数代码如下:
代码定位:org.pentaho.di.core.database.MySQLDatabaseMeta.getFieldDefinition()

public String getFieldDefinition( ValueMetaInterface v, String tk, String pks, boolean use_autoinc,
      boolean add_fieldname, boolean add_cr ) {
    String retval = "";

    String fieldname = v.getName();
    int length = v.getLength();
    int precision = v.getPrecision();

    if ( add_fieldname ) {
      retval += fieldname + " ";
    }

    int type = v.getType();
    switch ( type ) {
      case ValueMetaInterface.TYPE_DATE:
        retval += "DATETIME";
        break;
      case ValueMetaInterface.TYPE_BOOLEAN:
        if ( supportsBooleanDataType() ) {
          retval += "BOOLEAN";
        } else {
          retval += "CHAR(1)";
        }
        break;

      case ValueMetaInterface.TYPE_NUMBER:
      case ValueMetaInterface.TYPE_INTEGER:
      case ValueMetaInterface.TYPE_BIGNUMBER:
//    	if(isContainPk(pks,fieldname))
    	// Technical key
    	// Primary key
        if ((fieldname.equalsIgnoreCase(tk) || fieldname.equalsIgnoreCase(pks)) && !pks.contains(",")) 
        {
          if ( use_autoinc ) {
            retval += "BIGINT AUTO_INCREMENT NOT NULL PRIMARY KEY";
          } else {
            retval += "BIGINT NOT NULL PRIMARY KEY";
          }
        } else {
          // Integer values...
          if ( precision == 0 ) {
            if ( length > 9 ) {
              if ( length < 19 ) {
                // can hold signed values between -9223372036854775808 and 9223372036854775807
                // 18 significant digits
                retval += "BIGINT";
              } else {
                retval += "DECIMAL(" + length + ")";
              }
            } else {
              retval += "INT";
            }
          } else {
            // Floating point values...
            if ( length > 15 ) {
              retval += "DECIMAL(" + length;
              if ( precision > 0 ) {
                retval += ", " + precision;
              }
              retval += ")";
            } else {
              // A double-precision floating-point number is accurate to approximately 15 decimal places.
              // http://mysql.mirrors-r-us.net/doc/refman/5.1/en/numeric-type-overview.html
              retval += "DOUBLE";
            }
          }
        }
        break;
      case ValueMetaInterface.TYPE_STRING:   // 当类型为nvarchar时不设置primary key
        if ( length > 0 ) {
          if ( length == 1 ) {
            retval += "CHAR(1) ";
          } else if ( length < 256 ) {
            retval += "VARCHAR(" + length + ")";
          } else if ( length < 65536 ) {
            retval += "TEXT";
          } else if ( length < 16777216 ) {
            retval += "MEDIUMTEXT";
          } else {
            retval += "LONGTEXT";
          }
        } else {
          retval += "TINYTEXT";
        }
        // 修改kettle中varchar不能设置主键问题
        if ((fieldname.equalsIgnoreCase(tk) || fieldname.equalsIgnoreCase(pks)) && !pks.contains(","))
        {
              if ( use_autoinc ) {
                retval += " NOT NULL PRIMARY KEY";
              } else {
                retval += " NOT NULL PRIMARY KEY";
              }
            }
        break;
      case ValueMetaInterface.TYPE_BINARY:
        retval += "LONGBLOB";
        break;
      default:
        retval += " UNKNOWN";
        break;
    }

    if ( add_cr ) {
      retval += Const.CR;
    }

    return retval;
  }

从代码中看出,如果主键类型为nvarchar的字段,v.getType = 2,走case为2时,就不会给该这段设置primary key了,问题就出现在这里。加中文注释的地方就是我修改后的。

虽然kettle在构造表字段完成后会判断处理主键,但如果是跨库的情况下,获取到的Connection判断就会出现问题。因为取到的Connection是目标库的,此代码在Database类中:

if ( tk != null ) {
      if ( databaseMeta.requiresCreateTablePrimaryKeyAppend() ) { 
        retval.append( ", PRIMARY KEY (" ).append( tk ).append( ")" ).append( Const.CR );
      }
    }

    // Primary keys
    if ( pk != null ) {
      if ( databaseMeta.requiresCreateTablePrimaryKeyAppend() ) { 
        retval.append( ", PRIMARY KEY (" ).append( pk ).append( ")" ).append( Const.CR );
      }
    }

因为是跨库迁移,上面Database类中的最后设置主键对我是没起作用的, 经过调试,也发现了kettle的一些问题,databaseMeta.requiresCreateTablePrimaryKeyAppend() 该判断不满足条件,所以结合自己的需求修改后,成功将主键问题解决了。

最后附上一张图,以证ok,修改源码后迁移过程:




文章更新中。。。



特别感谢:


评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值