本文转自:http://www.360doc.com/content/15/0811/09/27089714_490902857.shtml
学习使用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;
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;
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现在的版本已经修改好了