1. 程式人生 > >kettle能抽取oracle的clob欄位麼?

kettle能抽取oracle的clob欄位麼?

學習使用kettle,在學習的過程中遇到一些連線資料庫的問題,經過一番努力之後,終於找到解決方案,現將遇到的問題和解決方案公佈如下,有不對的地方請大家指正。 
問題一:用spoon設計了一個轉換,主要功能是從資料檔案中讀取記錄,然後直接存入資料庫(我們使用的是IBM DB2)。在執行轉換的過程中,遇到了如下異常:

2006/11/03 16:04:12 - 資料庫輸出.0 - ERROR (version 2.3.1, build 63 from 2006/09/14 12:04:05 @ sam) : An error occurred intialising this step:
2006/11/03 16:04:12 - 資料庫輸出.0 - ERROR (version 2.3.1, build 63 from 2006/09/14 12:04:05 @ sam) : Error occured while trying to connect to the database
2006/11/03 16:04:12 - 資料庫輸出.0 - ERROR (version 2.3.1, build 63 from 2006/09/14 12:04:05 @ sam) :
2006/11/03 16:04:12 - 資料庫輸出.0 - ERROR (version 2.3.1, build 63 from 2006/09/14 12:04:05 @ sam) : Error connecting to database: (using class com.ibm.db2.jcc.DB2Driver)
2006/11/03 16:04:12 - 資料庫輸出.0 - ERROR (version 2.3.1, build 63 from 2006/09/14 12:04:05 @ sam) : Unicode string can't convert to Ebcdic string
2006/11/03 16:04:12 - 資料庫輸出 - ERROR (version 2.3.1, build 63 from 2006/09/14 12:04:05 @ sam) : 錯誤初始化步驟[資料庫輸出]
2006/11/03 16:04:12 - be.ibridge.kettle.trans.Trans - ERROR (version 2.3.1, build 63 from 2006/09/14 12:04:05 @ sam) : !Trans.Log.StepFailedToInit!
2006/11/03 16:04:12 - be.ibridge.kettle.trans.Trans - ERROR (version 2.3.1, build 63 from 2006/09/14 12:04:05 @ sam) : 無法初始化至少一個步驟. 執行無法開始!

在遇到該異常之前,我已經對資料庫連線進行了測試,完全可以正常連線。之後我又使用spoon的SQL編輯器連線資料庫並插入記錄,仍然沒有任何異 常。換為連線MySql然後執行該轉換也沒有問題,資料可以正常的寫入資料庫。我們首先考慮到的是spoon使用的字符集和DB2的字符集不匹配。於是更 改資料庫的字符集,將其改為utf-8,然後再執行轉換,依然丟擲上面的異常。後來在查閱資料的時候發現DB2 type 4的jdbc驅動可能存在字元編碼轉換的問題,於是察看spoon使用的DB2 jdbc驅動的型別,正是type4的驅動!於是考慮配置spoon,使其使用type3的jdbc驅動,讓我們鬱悶的是spoon里根本沒有對驅動型別 進行配置的地方!無奈中,我們找出了kettle的原始碼,幾經周折,最終在be.ibridge.kettle.core.database包中的 DB2DatabaseMeta.java檔案中找到了kettle的DB2連線配置,源程式如下:

public String getDriverClass()
...{
if (getAccessType()==DatabaseMeta.TYPE_ACCESS_ODBC)
...{    return "sun.jdbc.odbc.JdbcOdbcDriver";   }
else
...{
   return "com.ibm.db2.jcc.DB2Driver";
}
}

public String getURL()
...{
if (getAccessType()==DatabaseMeta.TYPE_ACCESS_ODBC)
...{
   return "jdbc:odbc:"+getDatabaseName();
}
else
...{
   return "jdbc:db2://"+getHostname()+":"+getDatabasePortNumberString()+"/"+getDatabaseName();
}
}對這兩個方法進行修改,改為以下程式碼: public String getDriverClass()
...{
if (getAccessType()==DatabaseMeta.TYPE_ACCESS_ODBC)
...{return "sun.jdbc.odbc.JdbcOdbcDriver";}
else
...{return "com.ibm.db2.jdbc.net.DB2Driver";}

}
public String getURL()
...{
if (getAccessType()==DatabaseMeta.TYPE_ACCESS_ODBC)
...{return "jdbc:odbc:"+getDatabaseName();}

else
...{return "jdbc:db2:"+getHostname()+":"+getDatabaseName();}
編譯修改後的檔案,用新的DB2DatabaseMeta.class代替kettle原來的檔案,重新打包(注意要把DB2自帶的驅動程式包也打進去),重新執行該轉換,一切ok啦!

問題二:無論使用spoon還是使用chef的時候都會詢問你有沒有資源庫,第一次執行時當然是沒有了,那麼就新建吧。這時,讓人鬱悶的東西出現了,在向資料庫(這裡還是DB2 啦)中建表的時候會出現如下異常,導致資源庫建立失敗:


Couldn't execute SQL: CREATE TABLE R_NOTE
(
ID_NOTE DECIMAL(9)
, VALUE_STR CLOBBLOB(2000000) UNKNOWN
, GUI_LOCATION_X DECIMAL(6)
, GUI_LOCATION_Y DECIMAL(6)
, GUI_LOCATION_WIDTH DECIMAL(6)
, GUI_LOCATION_HEIGHT DECIMAL(6)
)

DB2 SQL error: SQLCODE: -104, SQLSTATE: 42601, SQLERRMC: CLOBBLOB;IMAL(9) , VALUE_STR;<clob>

java.lang.reflect.InvocationTargetException: Error creating or upgrading repository:

Couldn't execute SQL: CREATE TABLE R_NOTE
(
ID_NOTE DECIMAL(9)
, VALUE_STR CLOBBLOB(2000000) UNKNOWN
, GUI_LOCATION_X DECIMAL(6)
, GUI_LOCATION_Y DECIMAL(6)
, GUI_LOCATION_WIDTH DECIMAL(6)
, GUI_LOCATION_HEIGHT DECIMAL(6)
)

DB2 SQL error: SQLCODE: -104, SQLSTATE: 42601, SQLERRMC: CLOBBLOB;IMAL(9) , VALUE_STR;<clob>

at be.ibridge.kettle.repository.dialog.UpgradeRepositoryProgressDialog$1.run(UpgradeRepositoryProgressDialog.java:66)
at org.eclipse.jface.operation.ModalContext.runInCurrentThread(ModalContext.java:346)
at org.eclipse.jface.operation.ModalContext.run(ModalContext.java:291)
at org.eclipse.jface.dialogs.ProgressMonitorDialog.run(ProgressMonitorDialog.java:447)
at be.ibridge.kettle.repository.dialog.UpgradeRepositoryProgressDialog.open(UpgradeRepositoryProgressDialog.java:74)
at be.ibridge.kettle.repository.dialog.RepositoryDialog.create(RepositoryDialog.java:450)
at be.ibridge.kettle.repository.dialog.RepositoryDialog.access$800(RepositoryDialog.java:64)
at be.ibridge.kettle.repository.dialog.RepositoryDialog$5.handleEvent(RepositoryDialog.java:267)
at org.eclipse.swt.widgets.EventTable.sendEvent(EventTable.java:66)
at org.eclipse.swt.widgets.Widget.sendEvent(Widget.java:928)
at org.eclipse.swt.widgets.Display.runDeferredEvents(Display.java:3348)
at org.eclipse.swt.widgets.Display.readAndDispatch(Display.java:2968)
at be.ibridge.kettle.repository.dialog.RepositoryDialog.open(RepositoryDialog.java:291)
at be.ibridge.kettle.repository.dialog.RepositoriesDialog$3.widgetSelected(RepositoriesDialog.java:297)
。。。more異常資訊很長,但請大家注意上面紅色標記的異常。CLOBBLOB(2000000) UNKNOWN 覺不覺的這個型別很奇怪?沒錯,就是它導致建表失敗的。通過分析原始碼,最終還是在DB2DatabaseMeta.java中找到以下方法:

public String getFieldDefinition(Value v, String tk, String pk, 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 Value.VALUE_TYPE_DATE   : retval+="TIMESTAMP"; break;
case Value.VALUE_TYPE_BOOLEAN: retval+="CHARACTER(1)"; break;
case Value.VALUE_TYPE_NUMBER :
case Value.VALUE_TYPE_INTEGER:
case Value.VALUE_TYPE_BIGNUMBER:
   if (fieldname.equalsIgnoreCase(tk) && use_autoinc) // Technical key: auto increment field!
   ...{
   retval+="BIGINT NOT NULL GENERATED ALWAYS AS IDENTITY (START WITH 0, INCREMENT BY 1, NOCACHE)";
   }
   else
   ...{
   if (length>0)
   ...{
   retval+="DECIMAL("+length;
   if (precision>0)
   ...{
      retval+=", "+precision;
     }
     retval+=")";
    }
    else
    ...{
     retval+="FLOAT";
    }
   }
   break;
case Value.VALUE_TYPE_STRING:
   if (length>getMaxVARCHARLength() || length>=DatabaseMeta.CLOB_LENGTH)
   ...{
    retval+="CLOB";
   }
   else
   ...{
    retval+="VARCHAR";
    if (length>0)
    ...{
     retval+="("+length;
    }
    else
    ...{
     retval+="("; // Maybe use some default DB String length?
    }
    retval+=")";
    break;
   }
case Value.VALUE_TYPE_BINARY:
   if (length>getMaxVARCHARLength() || length>=DatabaseMeta.CLOB_LENGTH)
   ...{
    retval+="BLOB("length")";
   }
   else
   ...{
    if (length>0)
    ...{ 
        retval+="CHAR("length") FOR BIT DATA";
    }
    else
    ...{
     retval+="BLOB"; // not going to work, but very close
    }
    break;
   }   
default:
   retval+=" UNKNOWN";
   break;
}
if (add_cr) retval+=Const.CR;

return retval;
}注意方法中紅色標記的部分,在這裡竟然出現了嚴重的邏輯錯誤:case語句裡並不是每個分支都有break;因此當有滿足Value.VALUE_TYPE_STRING分支的if語句的條件出現,就會順序執行後面所有的分支(注意,滿足Value.VALUE_TYPE_STRING分支的if語句的條件同樣也滿足Value.VALUE_TYPE_BINARY 分支的if語句的條件)。這就導致了最終的字串變成了我們前面看到的那個奇怪的資料庫欄位型別。那麼解決方法就簡單了,修改的程式碼如下:


case Value.VALUE_TYPE_STRING:
   if (length>getMaxVARCHARLength() || length>=DatabaseMeta.CLOB_LENGTH)
   ...{
    retval+="CLOB";
   }
   else
   ...{
    retval+="VARCHAR";
    if (length>0)
    ...{
     retval+="("+length;
    }
    else
    ...{
     retval+="("; // Maybe use some default DB String length?
    }
    retval+=")";
   }   break;
   case Value.VALUE_TYPE_BINARY:
   if (length>getMaxVARCHARLength() || length>=DatabaseMeta.CLOB_LENGTH)
   ...{
    retval+="BLOB("length")";
   }
   else
   ...{
    if (length>0)
    ...{ 
        retval+="CHAR("length") FOR BIT DATA";
    }
    else
    ...{
     retval+="BLOB"; // not going to work, but very close
    }
   }break;default: 。。。。。。這樣就好了,不過第二個問題好像kettle現在的版本已經修改好了